Skip to content

Commit a1f7ff6

Browse files
feat: Add optional CancellationToken parameter to SetProviderAsync (#638)
* Add CancellationToken optional parameter to SetProviderAsync Signed-off-by: Kyle Julian <[email protected]> * Address gemini review comments Signed-off-by: Kyle Julian <[email protected]> * Address API review comments Signed-off-by: Kyle Julian <[email protected]> --------- Signed-off-by: Kyle Julian <[email protected]>
1 parent 6b6f478 commit a1f7ff6

File tree

2 files changed

+70
-4
lines changed

2 files changed

+70
-4
lines changed

src/OpenFeature/Api.cs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,39 @@ internal Api() { }
4040
/// </summary>
4141
/// <remarks>The provider cannot be set to null. Attempting to set the provider to null has no effect. May throw an exception if <paramref name="featureProvider"/> cannot be initialized.</remarks>
4242
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
43-
public async Task SetProviderAsync(FeatureProvider featureProvider)
43+
/// <returns>A <see cref="Task"/> that completes once Provider initialization is complete.</returns>
44+
public Task SetProviderAsync(FeatureProvider featureProvider)
45+
{
46+
return this.SetProviderAsync(featureProvider, CancellationToken.None);
47+
}
48+
49+
/// <summary>
50+
/// Sets the default feature provider. In order to wait for the provider to be set, and initialization to complete,
51+
/// await the returned task.
52+
/// </summary>
53+
/// <remarks>The provider cannot be set to null. Attempting to set the provider to null has no effect. May throw an exception if <paramref name="featureProvider"/> cannot be initialized.</remarks>
54+
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
55+
/// <param name="cancellationToken">Propagates notification that the provider initialization should be canceled.</param>
56+
/// <returns>A <see cref="Task"/> that completes once Provider initialization is complete.</returns>
57+
public async Task SetProviderAsync(FeatureProvider featureProvider, CancellationToken cancellationToken)
4458
{
4559
this._eventExecutor.RegisterDefaultFeatureProvider(featureProvider);
46-
await this._repository.SetProviderAsync(featureProvider, this.GetContext(), this.AfterInitializationAsync, this.AfterErrorAsync)
60+
await this._repository.SetProviderAsync(featureProvider, this.GetContext(), this.AfterInitializationAsync, this.AfterErrorAsync, cancellationToken)
4761
.ConfigureAwait(false);
62+
}
4863

64+
/// <summary>
65+
/// Binds the feature provider to the given domain. In order to wait for the provider to be set, and
66+
/// initialization to complete, await the returned task.
67+
/// </summary>
68+
/// <remarks>The provider cannot be set to null. Attempting to set the provider to null has no effect. May throw an exception if <paramref name="featureProvider"/> cannot be initialized.</remarks>
69+
/// <param name="domain">An identifier which logically binds clients with providers</param>
70+
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
71+
/// <exception cref="ArgumentNullException">domain cannot be null or empty</exception>
72+
/// <returns>A <see cref="Task"/> that completes once Provider initialization is complete.</returns>
73+
public Task SetProviderAsync(string domain, FeatureProvider featureProvider)
74+
{
75+
return this.SetProviderAsync(domain, featureProvider, CancellationToken.None);
4976
}
5077

5178
/// <summary>
@@ -55,15 +82,17 @@ await this._repository.SetProviderAsync(featureProvider, this.GetContext(), this
5582
/// <remarks>The provider cannot be set to null. Attempting to set the provider to null has no effect. May throw an exception if <paramref name="featureProvider"/> cannot be initialized.</remarks>
5683
/// <param name="domain">An identifier which logically binds clients with providers</param>
5784
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
85+
/// <param name="cancellationToken">Propagates notification that the provider initialization should be canceled.</param>
5886
/// <exception cref="ArgumentNullException">domain cannot be null or empty</exception>
59-
public async Task SetProviderAsync(string domain, FeatureProvider featureProvider)
87+
/// <returns>A <see cref="Task"/> that completes once Provider initialization is complete.</returns>
88+
public async Task SetProviderAsync(string domain, FeatureProvider featureProvider, CancellationToken cancellationToken)
6089
{
6190
if (string.IsNullOrWhiteSpace(domain))
6291
{
6392
throw new ArgumentNullException(nameof(domain));
6493
}
6594
this._eventExecutor.RegisterClientFeatureProvider(domain, featureProvider);
66-
await this._repository.SetProviderAsync(domain, featureProvider, this.GetContext(), this.AfterInitializationAsync, this.AfterErrorAsync)
95+
await this._repository.SetProviderAsync(domain, featureProvider, this.GetContext(), this.AfterInitializationAsync, this.AfterErrorAsync, cancellationToken)
6796
.ConfigureAwait(false);
6897
}
6998

test/OpenFeature.Tests/OpenFeatureTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,43 @@ public async Task OpenFeature_Should_Initialize_Provider()
3434
await providerMockNamed.Received(1).InitializeAsync(Api.Instance.GetContext());
3535
}
3636

37+
[Fact]
38+
public async Task OpenFeature_Should_Initialize_Provider_WithCancellationToken()
39+
{
40+
var providerMockDefault = Substitute.For<FeatureProvider>();
41+
providerMockDefault.Status.Returns(ProviderStatus.NotReady);
42+
43+
using var cancellationTokenSource = new CancellationTokenSource();
44+
var cancellationToken = cancellationTokenSource.Token;
45+
46+
await Api.Instance.SetProviderAsync(providerMockDefault, cancellationToken);
47+
await providerMockDefault.Received(1).InitializeAsync(Api.Instance.GetContext(), cancellationToken);
48+
49+
var providerMockNamed = Substitute.For<FeatureProvider>();
50+
providerMockNamed.Status.Returns(ProviderStatus.NotReady);
51+
52+
await Api.Instance.SetProviderAsync("the-name", providerMockNamed, cancellationToken);
53+
await providerMockNamed.Received(1).InitializeAsync(Api.Instance.GetContext(), cancellationToken);
54+
}
55+
56+
[Fact]
57+
public async Task OpenFeature_Should_Handle_Cancellation_During_Initialization()
58+
{
59+
using var cancellationTokenSource = new CancellationTokenSource();
60+
cancellationTokenSource.Cancel();
61+
var cancellationToken = cancellationTokenSource.Token;
62+
63+
var providerMockDefault = Substitute.For<FeatureProvider>();
64+
providerMockDefault.InitializeAsync(Arg.Any<EvaluationContext>(), cancellationToken)
65+
.Returns(ci => Task.FromCanceled(cancellationToken));
66+
67+
await Assert.ThrowsAsync<TaskCanceledException>(() =>
68+
Api.Instance.SetProviderAsync(providerMockDefault, cancellationToken));
69+
70+
await providerMockDefault.Received(1).InitializeAsync(Api.Instance.GetContext(), cancellationToken);
71+
Assert.Equal(ProviderStatus.Error, providerMockDefault.Status);
72+
}
73+
3774
[Fact]
3875
[Specification("1.1.2.3",
3976
"The provider mutator function MUST invoke the shutdown function on the previously registered provider once it's no longer being used to resolve flag values.")]

0 commit comments

Comments
 (0)