Skip to content

Commit 2547a57

Browse files
authored
refactor: Simplify Provider Repository (#515)
* refactor: simplify initialization and shutdown methods with cancellation support Signed-off-by: André Silva <[email protected]> * refactor: change visibility of GetProvider and ShutdownAsync methods to internal Signed-off-by: André Silva <[email protected]> * fix: correct logger instance type in ProviderRepository Signed-off-by: André Silva <[email protected]> * fix: update preprocessor directives for compatibility with .NET Standard Signed-off-by: André Silva <[email protected]> * fix: update SetProviderAsync method to enforce non-null domain parameter Signed-off-by: André Silva <[email protected]> * fix: enhance documentation for GetProvider and FeatureClient constructor parameters Signed-off-by: André Silva <[email protected]> --------- Signed-off-by: André Silva <[email protected]>
1 parent 20d1f37 commit 2547a57

File tree

2 files changed

+30
-29
lines changed

2 files changed

+30
-29
lines changed

src/OpenFeature/Api.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public FeatureProvider GetProvider()
8585
/// Gets the feature provider with given domain
8686
/// </summary>
8787
/// <param name="domain">An identifier which logically binds clients with providers</param>
88-
/// <returns>A provider associated with the given domain, if domain is empty or doesn't
88+
/// <returns>A provider associated with the given domain, if domain is empty, null, whitespace or doesn't
8989
/// have a corresponding provider the default provider will be returned</returns>
9090
public FeatureProvider GetProvider(string domain)
9191
{
@@ -114,7 +114,7 @@ public FeatureProvider GetProvider(string domain)
114114
/// <summary>
115115
/// Create a new instance of <see cref="FeatureClient"/> using the current provider
116116
/// </summary>
117-
/// <param name="name">Name of client</param>
117+
/// <param name="name">Name of client, if the <paramref name="name"/> is not provided a default name will be used</param>
118118
/// <param name="version">Version of client</param>
119119
/// <param name="logger">Logger instance used by client</param>
120120
/// <param name="context">Context given to this client</param>

src/OpenFeature/ProviderRepository.cs

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,18 @@
44
using OpenFeature.Constant;
55
using OpenFeature.Model;
66

7-
87
namespace OpenFeature;
98

109
/// <summary>
1110
/// This class manages the collection of providers, both default and named, contained by the API.
1211
/// </summary>
1312
internal sealed partial class ProviderRepository : IAsyncDisposable
1413
{
15-
private ILogger _logger = NullLogger<EventExecutor>.Instance;
14+
private ILogger _logger = NullLogger<ProviderRepository>.Instance;
1615

1716
private FeatureProvider _defaultProvider = new NoOpFeatureProvider();
1817

19-
private readonly ConcurrentDictionary<string, FeatureProvider> _featureProviders =
20-
new ConcurrentDictionary<string, FeatureProvider>();
18+
private readonly ConcurrentDictionary<string, FeatureProvider> _featureProviders = new();
2119

2220
/// The reader/writer locks is not disposed because the singleton instance should never be disposed.
2321
///
@@ -29,7 +27,7 @@ internal sealed partial class ProviderRepository : IAsyncDisposable
2927
/// The second is that a concurrent collection doesn't provide any ordering, so we could check a provider
3028
/// as it was being added or removed such as two concurrent calls to SetProvider replacing multiple instances
3129
/// of that provider under different names.
32-
private readonly ReaderWriterLockSlim _providersLock = new ReaderWriterLockSlim();
30+
private readonly ReaderWriterLockSlim _providersLock = new();
3331

3432
public async ValueTask DisposeAsync()
3533
{
@@ -53,11 +51,13 @@ public async ValueTask DisposeAsync()
5351
/// called if an error happens during the initialization of the provider, only called if the provider needed
5452
/// initialization
5553
/// </param>
56-
public async Task SetProviderAsync(
54+
/// <param name="cancellationToken">a cancellation token to cancel the operation</param>
55+
internal async Task SetProviderAsync(
5756
FeatureProvider? featureProvider,
5857
EvaluationContext context,
5958
Func<FeatureProvider, Task>? afterInitSuccess = null,
60-
Func<FeatureProvider, Exception, Task>? afterInitError = null)
59+
Func<FeatureProvider, Exception, Task>? afterInitError = null,
60+
CancellationToken cancellationToken = default)
6161
{
6262
// Cannot unset the feature provider.
6363
if (featureProvider == null)
@@ -79,22 +79,23 @@ public async Task SetProviderAsync(
7979
this._defaultProvider = featureProvider;
8080
// We want to allow shutdown to happen concurrently with initialization, and the caller to not
8181
// wait for it.
82-
_ = this.ShutdownIfUnusedAsync(oldProvider);
82+
_ = this.ShutdownIfUnusedAsync(oldProvider, cancellationToken);
8383
}
8484
finally
8585
{
8686
this._providersLock.ExitWriteLock();
8787
}
8888

89-
await InitProviderAsync(this._defaultProvider, context, afterInitSuccess, afterInitError)
89+
await InitProviderAsync(this._defaultProvider, context, afterInitSuccess, afterInitError, cancellationToken)
9090
.ConfigureAwait(false);
9191
}
9292

9393
private static async Task InitProviderAsync(
9494
FeatureProvider? newProvider,
9595
EvaluationContext context,
9696
Func<FeatureProvider, Task>? afterInitialization,
97-
Func<FeatureProvider, Exception, Task>? afterError)
97+
Func<FeatureProvider, Exception, Task>? afterError,
98+
CancellationToken cancellationToken = default)
9899
{
99100
if (newProvider == null)
100101
{
@@ -104,7 +105,7 @@ private static async Task InitProviderAsync(
104105
{
105106
try
106107
{
107-
await newProvider.InitializeAsync(context).ConfigureAwait(false);
108+
await newProvider.InitializeAsync(context, cancellationToken).ConfigureAwait(false);
108109
if (afterInitialization != null)
109110
{
110111
await afterInitialization.Invoke(newProvider).ConfigureAwait(false);
@@ -134,15 +135,15 @@ private static async Task InitProviderAsync(
134135
/// initialization
135136
/// </param>
136137
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to cancel any async side effects.</param>
137-
public async Task SetProviderAsync(string? domain,
138+
internal async Task SetProviderAsync(string domain,
138139
FeatureProvider? featureProvider,
139140
EvaluationContext context,
140141
Func<FeatureProvider, Task>? afterInitSuccess = null,
141142
Func<FeatureProvider, Exception, Task>? afterInitError = null,
142143
CancellationToken cancellationToken = default)
143144
{
144145
// Cannot set a provider for a null domain.
145-
if (domain == null)
146+
if (string.IsNullOrWhiteSpace(domain))
146147
{
147148
return;
148149
}
@@ -166,21 +167,21 @@ public async Task SetProviderAsync(string? domain,
166167

167168
// We want to allow shutdown to happen concurrently with initialization, and the caller to not
168169
// wait for it.
169-
_ = this.ShutdownIfUnusedAsync(oldProvider);
170+
_ = this.ShutdownIfUnusedAsync(oldProvider, cancellationToken);
170171
}
171172
finally
172173
{
173174
this._providersLock.ExitWriteLock();
174175
}
175176

176-
await InitProviderAsync(featureProvider, context, afterInitSuccess, afterInitError).ConfigureAwait(false);
177+
await InitProviderAsync(featureProvider, context, afterInitSuccess, afterInitError, cancellationToken).ConfigureAwait(false);
177178
}
178179

179180
/// <remarks>
180181
/// Shutdown the feature provider if it is unused. This must be called within a write lock of the _providersLock.
181182
/// </remarks>
182183
private async Task ShutdownIfUnusedAsync(
183-
FeatureProvider? targetProvider)
184+
FeatureProvider? targetProvider, CancellationToken cancellationToken = default)
184185
{
185186
if (ReferenceEquals(this._defaultProvider, targetProvider))
186187
{
@@ -192,7 +193,7 @@ private async Task ShutdownIfUnusedAsync(
192193
return;
193194
}
194195

195-
await this.SafeShutdownProviderAsync(targetProvider).ConfigureAwait(false);
196+
await this.SafeShutdownProviderAsync(targetProvider, cancellationToken).ConfigureAwait(false);
196197
}
197198

198199
/// <remarks>
@@ -204,7 +205,7 @@ private async Task ShutdownIfUnusedAsync(
204205
/// it would not be meaningful to emit an error.
205206
/// </para>
206207
/// </remarks>
207-
private async Task SafeShutdownProviderAsync(FeatureProvider? targetProvider)
208+
private async Task SafeShutdownProviderAsync(FeatureProvider? targetProvider, CancellationToken cancellationToken = default)
208209
{
209210
if (targetProvider == null)
210211
{
@@ -213,15 +214,15 @@ private async Task SafeShutdownProviderAsync(FeatureProvider? targetProvider)
213214

214215
try
215216
{
216-
await targetProvider.ShutdownAsync().ConfigureAwait(false);
217+
await targetProvider.ShutdownAsync(cancellationToken).ConfigureAwait(false);
217218
}
218219
catch (Exception ex)
219220
{
220221
this.ErrorShuttingDownProvider(targetProvider.GetMetadata()?.Name, ex);
221222
}
222223
}
223224

224-
public FeatureProvider GetProvider()
225+
internal FeatureProvider GetProvider()
225226
{
226227
this._providersLock.EnterReadLock();
227228
try
@@ -234,16 +235,16 @@ public FeatureProvider GetProvider()
234235
}
235236
}
236237

237-
public FeatureProvider GetProvider(string? domain)
238+
internal FeatureProvider GetProvider(string? domain)
238239
{
239-
#if NET6_0_OR_GREATER
240-
if (string.IsNullOrEmpty(domain))
240+
#if NETFRAMEWORK || NETSTANDARD
241+
// This is a workaround for the issue in .NET Framework where string.IsNullOrEmpty is not nullable compatible.
242+
if (domain == null)
241243
{
242244
return this.GetProvider();
243245
}
244246
#else
245-
// This is a workaround for the issue in .NET Framework where string.IsNullOrEmpty is not nullable compatible.
246-
if (domain == null || string.IsNullOrEmpty(domain))
247+
if (string.IsNullOrWhiteSpace(domain))
247248
{
248249
return this.GetProvider();
249250
}
@@ -254,7 +255,7 @@ public FeatureProvider GetProvider(string? domain)
254255
: this.GetProvider();
255256
}
256257

257-
public async Task ShutdownAsync(Action<FeatureProvider, Exception>? afterError = null, CancellationToken cancellationToken = default)
258+
internal async Task ShutdownAsync(Action<FeatureProvider, Exception>? afterError = null, CancellationToken cancellationToken = default)
258259
{
259260
var providers = new HashSet<FeatureProvider>();
260261
this._providersLock.EnterWriteLock();
@@ -278,7 +279,7 @@ public async Task ShutdownAsync(Action<FeatureProvider, Exception>? afterError =
278279
foreach (var targetProvider in providers)
279280
{
280281
// We don't need to take any actions after shutdown.
281-
await this.SafeShutdownProviderAsync(targetProvider).ConfigureAwait(false);
282+
await this.SafeShutdownProviderAsync(targetProvider, cancellationToken).ConfigureAwait(false);
282283
}
283284
}
284285

0 commit comments

Comments
 (0)