diff --git a/src/OpenFeature/Api.cs b/src/OpenFeature/Api.cs
index cea661398..93deb31cb 100644
--- a/src/OpenFeature/Api.cs
+++ b/src/OpenFeature/Api.cs
@@ -85,7 +85,7 @@ public FeatureProvider GetProvider()
/// Gets the feature provider with given domain
///
/// An identifier which logically binds clients with providers
- /// A provider associated with the given domain, if domain is empty or doesn't
+ /// A provider associated with the given domain, if domain is empty, null, whitespace or doesn't
/// have a corresponding provider the default provider will be returned
public FeatureProvider GetProvider(string domain)
{
@@ -114,7 +114,7 @@ public FeatureProvider GetProvider(string domain)
///
/// Create a new instance of using the current provider
///
- /// Name of client
+ /// Name of client, if the is not provided a default name will be used
/// Version of client
/// Logger instance used by client
/// Context given to this client
diff --git a/src/OpenFeature/ProviderRepository.cs b/src/OpenFeature/ProviderRepository.cs
index 4f938940d..4cea63b08 100644
--- a/src/OpenFeature/ProviderRepository.cs
+++ b/src/OpenFeature/ProviderRepository.cs
@@ -4,7 +4,6 @@
using OpenFeature.Constant;
using OpenFeature.Model;
-
namespace OpenFeature;
///
@@ -12,12 +11,11 @@ namespace OpenFeature;
///
internal sealed partial class ProviderRepository : IAsyncDisposable
{
- private ILogger _logger = NullLogger.Instance;
+ private ILogger _logger = NullLogger.Instance;
private FeatureProvider _defaultProvider = new NoOpFeatureProvider();
- private readonly ConcurrentDictionary _featureProviders =
- new ConcurrentDictionary();
+ private readonly ConcurrentDictionary _featureProviders = new();
/// The reader/writer locks is not disposed because the singleton instance should never be disposed.
///
@@ -29,7 +27,7 @@ internal sealed partial class ProviderRepository : IAsyncDisposable
/// The second is that a concurrent collection doesn't provide any ordering, so we could check a provider
/// as it was being added or removed such as two concurrent calls to SetProvider replacing multiple instances
/// of that provider under different names.
- private readonly ReaderWriterLockSlim _providersLock = new ReaderWriterLockSlim();
+ private readonly ReaderWriterLockSlim _providersLock = new();
public async ValueTask DisposeAsync()
{
@@ -53,11 +51,13 @@ public async ValueTask DisposeAsync()
/// called if an error happens during the initialization of the provider, only called if the provider needed
/// initialization
///
- public async Task SetProviderAsync(
+ /// a cancellation token to cancel the operation
+ internal async Task SetProviderAsync(
FeatureProvider? featureProvider,
EvaluationContext context,
Func? afterInitSuccess = null,
- Func? afterInitError = null)
+ Func? afterInitError = null,
+ CancellationToken cancellationToken = default)
{
// Cannot unset the feature provider.
if (featureProvider == null)
@@ -79,14 +79,14 @@ public async Task SetProviderAsync(
this._defaultProvider = featureProvider;
// We want to allow shutdown to happen concurrently with initialization, and the caller to not
// wait for it.
- _ = this.ShutdownIfUnusedAsync(oldProvider);
+ _ = this.ShutdownIfUnusedAsync(oldProvider, cancellationToken);
}
finally
{
this._providersLock.ExitWriteLock();
}
- await InitProviderAsync(this._defaultProvider, context, afterInitSuccess, afterInitError)
+ await InitProviderAsync(this._defaultProvider, context, afterInitSuccess, afterInitError, cancellationToken)
.ConfigureAwait(false);
}
@@ -94,7 +94,8 @@ private static async Task InitProviderAsync(
FeatureProvider? newProvider,
EvaluationContext context,
Func? afterInitialization,
- Func? afterError)
+ Func? afterError,
+ CancellationToken cancellationToken = default)
{
if (newProvider == null)
{
@@ -104,7 +105,7 @@ private static async Task InitProviderAsync(
{
try
{
- await newProvider.InitializeAsync(context).ConfigureAwait(false);
+ await newProvider.InitializeAsync(context, cancellationToken).ConfigureAwait(false);
if (afterInitialization != null)
{
await afterInitialization.Invoke(newProvider).ConfigureAwait(false);
@@ -134,7 +135,7 @@ private static async Task InitProviderAsync(
/// initialization
///
/// The to cancel any async side effects.
- public async Task SetProviderAsync(string? domain,
+ internal async Task SetProviderAsync(string domain,
FeatureProvider? featureProvider,
EvaluationContext context,
Func? afterInitSuccess = null,
@@ -142,7 +143,7 @@ public async Task SetProviderAsync(string? domain,
CancellationToken cancellationToken = default)
{
// Cannot set a provider for a null domain.
- if (domain == null)
+ if (string.IsNullOrWhiteSpace(domain))
{
return;
}
@@ -166,21 +167,21 @@ public async Task SetProviderAsync(string? domain,
// We want to allow shutdown to happen concurrently with initialization, and the caller to not
// wait for it.
- _ = this.ShutdownIfUnusedAsync(oldProvider);
+ _ = this.ShutdownIfUnusedAsync(oldProvider, cancellationToken);
}
finally
{
this._providersLock.ExitWriteLock();
}
- await InitProviderAsync(featureProvider, context, afterInitSuccess, afterInitError).ConfigureAwait(false);
+ await InitProviderAsync(featureProvider, context, afterInitSuccess, afterInitError, cancellationToken).ConfigureAwait(false);
}
///
/// Shutdown the feature provider if it is unused. This must be called within a write lock of the _providersLock.
///
private async Task ShutdownIfUnusedAsync(
- FeatureProvider? targetProvider)
+ FeatureProvider? targetProvider, CancellationToken cancellationToken = default)
{
if (ReferenceEquals(this._defaultProvider, targetProvider))
{
@@ -192,7 +193,7 @@ private async Task ShutdownIfUnusedAsync(
return;
}
- await this.SafeShutdownProviderAsync(targetProvider).ConfigureAwait(false);
+ await this.SafeShutdownProviderAsync(targetProvider, cancellationToken).ConfigureAwait(false);
}
///
@@ -204,7 +205,7 @@ private async Task ShutdownIfUnusedAsync(
/// it would not be meaningful to emit an error.
///
///
- private async Task SafeShutdownProviderAsync(FeatureProvider? targetProvider)
+ private async Task SafeShutdownProviderAsync(FeatureProvider? targetProvider, CancellationToken cancellationToken = default)
{
if (targetProvider == null)
{
@@ -213,7 +214,7 @@ private async Task SafeShutdownProviderAsync(FeatureProvider? targetProvider)
try
{
- await targetProvider.ShutdownAsync().ConfigureAwait(false);
+ await targetProvider.ShutdownAsync(cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -221,7 +222,7 @@ private async Task SafeShutdownProviderAsync(FeatureProvider? targetProvider)
}
}
- public FeatureProvider GetProvider()
+ internal FeatureProvider GetProvider()
{
this._providersLock.EnterReadLock();
try
@@ -234,16 +235,16 @@ public FeatureProvider GetProvider()
}
}
- public FeatureProvider GetProvider(string? domain)
+ internal FeatureProvider GetProvider(string? domain)
{
-#if NET6_0_OR_GREATER
- if (string.IsNullOrEmpty(domain))
+#if NETFRAMEWORK || NETSTANDARD
+ // This is a workaround for the issue in .NET Framework where string.IsNullOrEmpty is not nullable compatible.
+ if (domain == null)
{
return this.GetProvider();
}
#else
- // This is a workaround for the issue in .NET Framework where string.IsNullOrEmpty is not nullable compatible.
- if (domain == null || string.IsNullOrEmpty(domain))
+ if (string.IsNullOrWhiteSpace(domain))
{
return this.GetProvider();
}
@@ -254,7 +255,7 @@ public FeatureProvider GetProvider(string? domain)
: this.GetProvider();
}
- public async Task ShutdownAsync(Action? afterError = null, CancellationToken cancellationToken = default)
+ internal async Task ShutdownAsync(Action? afterError = null, CancellationToken cancellationToken = default)
{
var providers = new HashSet();
this._providersLock.EnterWriteLock();
@@ -278,7 +279,7 @@ public async Task ShutdownAsync(Action? afterError =
foreach (var targetProvider in providers)
{
// We don't need to take any actions after shutdown.
- await this.SafeShutdownProviderAsync(targetProvider).ConfigureAwait(false);
+ await this.SafeShutdownProviderAsync(targetProvider, cancellationToken).ConfigureAwait(false);
}
}