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); } }