Skip to content

Commit

Permalink
refactor: Cleanup + plumb cancellation tokens
Browse files Browse the repository at this point in the history
Signed-off-by: Austin Drenski <[email protected]>
  • Loading branch information
austindrenski committed Jan 17, 2024
1 parent a6062fe commit ef28ff5
Show file tree
Hide file tree
Showing 19 changed files with 629 additions and 591 deletions.
44 changes: 22 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ dotnet add package OpenFeature
public async Task Example()
{
// Register your feature flag provider
await Api.Instance.SetProvider(new InMemoryProvider());
await Api.Instance.SetProviderAsync(new InMemoryProvider());

// Create a new client
FeatureClient client = Api.Instance.GetClient();

// Evaluate your feature flag
bool v2Enabled = await client.GetBooleanValue("v2_enabled", false);
bool v2Enabled = await client.GetBooleanValueAsync("v2_enabled", false);

if ( v2Enabled )
{
Expand Down Expand Up @@ -112,7 +112,7 @@ If the provider you're looking for hasn't been created yet, see the [develop a p
Once you've added a provider as a dependency, it can be registered with OpenFeature like this:

```csharp
await Api.Instance.SetProvider(new MyProvider());
await Api.Instance.SetProviderAsync(new MyProvider());
```

In some situations, it may be beneficial to register multiple providers in the same application.
Expand Down Expand Up @@ -143,7 +143,7 @@ builder = EvaluationContext.Builder();
builder.Set("region", "us-east-1");
EvaluationContext reqCtx = builder.Build();

bool flagValue = await client.GetBooleanValue("some-flag", false, reqCtx);
bool flagValue = await client.GetBooleanValueAsync("some-flag", false, reqCtx);

```

Expand All @@ -164,7 +164,7 @@ var client = Api.Instance.GetClient();
client.AddHooks(new ExampleClientHook());

// add a hook for this evaluation only
var value = await client.GetBooleanValue("boolFlag", false, context, new FlagEvaluationOptions(new ExampleInvocationHook()));
var value = await client.GetBooleanValueAsync("boolFlag", false, context, new FlagEvaluationOptions(new ExampleInvocationHook()));
```

### Eventing
Expand Down Expand Up @@ -199,7 +199,7 @@ EventHandlerDelegate callback = EventHandler;
var myClient = Api.Instance.GetClient("my-client");

var provider = new ExampleProvider();
await Api.Instance.SetProvider(myClient.GetMetadata().Name, provider);
await Api.Instance.SetProviderAsync(myClient.GetMetadata().Name, provider);

myClient.AddHandler(ProviderEventTypes.ProviderReady, callback);
```
Expand All @@ -216,10 +216,10 @@ If a name has no associated provider, the global provider is used.

```csharp
// registering the default provider
await Api.Instance.SetProvider(new LocalProvider());
await Api.Instance.SetProviderAsync(new LocalProvider());

// registering a named provider
await Api.Instance.SetProvider("clientForCache", new CachedProvider());
await Api.Instance.SetProviderAsync("clientForCache", new CachedProvider());

// a client backed by default provider
FeatureClient clientDefault = Api.Instance.GetClient();
Expand All @@ -239,7 +239,7 @@ The OpenFeature API provides a close function to perform a cleanup of all regist

```csharp
// Shut down all providers
await Api.Instance.Shutdown();
await Api.Instance.ShutdownAsync();
```

## Extending
Expand All @@ -258,27 +258,27 @@ public class MyProvider : FeatureProvider
return new Metadata("My Provider");
}

public override Task<ResolutionDetails<bool>> ResolveBooleanValue(string flagKey, bool defaultValue, EvaluationContext context = null)
public override Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(string flagKey, bool defaultValue, EvaluationContext context = null, CancellationToken cancellationToken = default)
{
// resolve a boolean flag value
}

public override Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null)
public override Task<ResolutionDetails<double>> ResolveDoubleValueAsync(string flagKey, double defaultValue, EvaluationContext context = null, CancellationToken cancellationToken = default)
{
// resolve a double flag value
}

public override Task<ResolutionDetails<int>> ResolveIntegerValue(string flagKey, int defaultValue, EvaluationContext context = null)
public override Task<ResolutionDetails<int>> ResolveIntegerValueAsync(string flagKey, int defaultValue, EvaluationContext context = null, CancellationToken cancellationToken = default)
{
// resolve an int flag value
}

public override Task<ResolutionDetails<string>> ResolveStringValue(string flagKey, string defaultValue, EvaluationContext context = null)
public override Task<ResolutionDetails<string>> ResolveStringValueAsync(string flagKey, string defaultValue, EvaluationContext context = null, CancellationToken cancellationToken = default)
{
// resolve a string flag value
}

public override Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null)
public override Task<ResolutionDetails<Value>> ResolveStructureValueAsync(string flagKey, Value defaultValue, EvaluationContext context = null, CancellationToken cancellationToken = default)
{
// resolve an object flag value
}
Expand All @@ -290,30 +290,30 @@ public class MyProvider : FeatureProvider
To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency.
This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/dotnet-sdk-contrib) available under the OpenFeature organization.
Implement your own hook by conforming to the `Hook interface`.
To satisfy the interface, all methods (`Before`/`After`/`Finally`/`Error`) need to be defined.
To satisfy the interface, all methods (`BeforeAsync`/`AfterAsync`/`FinallyAsync`/`ErrorAsync`) need to be defined.

```csharp
public class MyHook : Hook
{
public Task<EvaluationContext> Before<T>(HookContext<T> context,
IReadOnlyDictionary<string, object> hints = null)
public Task<EvaluationContext> BeforeAsync<T>(HookContext<T> context,
IReadOnlyDictionary<string, object> hints = null, CancellationToken cancellationToken = default)
{
// code to run before flag evaluation
}

public virtual Task After<T>(HookContext<T> context, FlagEvaluationDetails<T> details,
IReadOnlyDictionary<string, object> hints = null)
public virtual Task AfterAsync<T>(HookContext<T> context, FlagEvaluationDetails<T> details,
IReadOnlyDictionary<string, object> hints = null, CancellationToken cancellationToken = default)
{
// code to run after successful flag evaluation
}

public virtual Task Error<T>(HookContext<T> context, Exception error,
IReadOnlyDictionary<string, object> hints = null)
public virtual Task ErrorAsync<T>(HookContext<T> context, Exception error,
IReadOnlyDictionary<string, object> hints = null, CancellationToken cancellationToken = default)
{
// code to run if there's an error during before hooks or during flag evaluation
}

public virtual Task Finally<T>(HookContext<T> context, IReadOnlyDictionary<string, object> hints = null)
public virtual Task FinallyAsync<T>(HookContext<T> context, IReadOnlyDictionary<string, object> hints = null, CancellationToken cancellationToken = default)
{
// code to run after all other stages, regardless of success/failure
}
Expand Down
17 changes: 10 additions & 7 deletions src/OpenFeature/Api.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ private Api() { }
/// </summary>
/// <remarks>The provider cannot be set to null. Attempting to set the provider to null has no effect.</remarks>
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
public async Task SetProvider(FeatureProvider featureProvider)
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
public async Task SetProviderAsync(FeatureProvider featureProvider, CancellationToken cancellationToken = default)
{
this.EventExecutor.RegisterDefaultFeatureProvider(featureProvider);
await this._repository.SetProvider(featureProvider, this.GetContext()).ConfigureAwait(false);
await this._repository.SetProviderAsync(featureProvider, this.GetContext(), cancellationToken: cancellationToken).ConfigureAwait(false);
}


Expand All @@ -56,10 +57,11 @@ public async Task SetProvider(FeatureProvider featureProvider)
/// </summary>
/// <param name="clientName">Name of client</param>
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
public async Task SetProvider(string clientName, FeatureProvider featureProvider)
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
public async Task SetProviderAsync(string clientName, FeatureProvider featureProvider, CancellationToken cancellationToken = default)
{
this.EventExecutor.RegisterClientFeatureProvider(clientName, featureProvider);
await this._repository.SetProvider(clientName, featureProvider, this.GetContext()).ConfigureAwait(false);
await this._repository.SetProviderAsync(clientName, featureProvider, this.GetContext(), cancellationToken: cancellationToken).ConfigureAwait(false);
}

/// <summary>
Expand Down Expand Up @@ -203,10 +205,11 @@ public EvaluationContext GetContext()
/// Once shut down is complete, API is reset and ready to use again.
/// </para>
/// </summary>
public async Task Shutdown()
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
public async Task ShutdownAsync(CancellationToken cancellationToken = default)
{
await this._repository.Shutdown().ConfigureAwait(false);
await this.EventExecutor.Shutdown().ConfigureAwait(false);
await this._repository.ShutdownAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
await this.EventExecutor.ShutdownAsync(cancellationToken).ConfigureAwait(false);
}

/// <inheritdoc />
Expand Down
12 changes: 6 additions & 6 deletions src/OpenFeature/EventExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace OpenFeature
{

internal delegate Task ShutdownDelegate();
internal delegate Task ShutdownDelegate(CancellationToken cancellationToken);

internal class EventExecutor
{
Expand Down Expand Up @@ -327,9 +327,9 @@ private void InvokeEventHandler(EventHandlerDelegate eventHandler, Event e)
}
}

public async Task Shutdown()
public async Task ShutdownAsync(CancellationToken cancellationToken = default)
{
await this._shutdownDelegate().ConfigureAwait(false);
await this._shutdownDelegate(cancellationToken).ConfigureAwait(false);
}

internal void SetShutdownDelegate(ShutdownDelegate del)
Expand All @@ -338,13 +338,13 @@ internal void SetShutdownDelegate(ShutdownDelegate del)
}

// Method to signal shutdown
private async Task SignalShutdownAsync()
private async Task SignalShutdownAsync(CancellationToken cancellationToken)
{
// Enqueue a shutdown signal
await this.EventChannel.Writer.WriteAsync(new ShutdownSignal()).ConfigureAwait(false);
await this.EventChannel.Writer.WriteAsync(new ShutdownSignal(), cancellationToken).ConfigureAwait(false);

// Wait for the processing loop to acknowledge the shutdown
await this._shutdownSemaphore.WaitAsync().ConfigureAwait(false);
await this._shutdownSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
}
}

Expand Down
34 changes: 21 additions & 13 deletions src/OpenFeature/FeatureProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using OpenFeature.Constant;
Expand Down Expand Up @@ -43,49 +44,54 @@ public abstract class FeatureProvider
/// <param name="flagKey">Feature flag key</param>
/// <param name="defaultValue">Default value</param>
/// <param name="context"><see cref="EvaluationContext"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns><see cref="ResolutionDetails{T}"/></returns>
public abstract Task<ResolutionDetails<bool>> ResolveBooleanValue(string flagKey, bool defaultValue,
EvaluationContext context = null);
public abstract Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(string flagKey, bool defaultValue,
EvaluationContext context = null, CancellationToken cancellationToken = default);

/// <summary>
/// Resolves a string feature flag
/// </summary>
/// <param name="flagKey">Feature flag key</param>
/// <param name="defaultValue">Default value</param>
/// <param name="context"><see cref="EvaluationContext"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns><see cref="ResolutionDetails{T}"/></returns>
public abstract Task<ResolutionDetails<string>> ResolveStringValue(string flagKey, string defaultValue,
EvaluationContext context = null);
public abstract Task<ResolutionDetails<string>> ResolveStringValueAsync(string flagKey, string defaultValue,
EvaluationContext context = null, CancellationToken cancellationToken = default);

/// <summary>
/// Resolves a integer feature flag
/// </summary>
/// <param name="flagKey">Feature flag key</param>
/// <param name="defaultValue">Default value</param>
/// <param name="context"><see cref="EvaluationContext"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns><see cref="ResolutionDetails{T}"/></returns>
public abstract Task<ResolutionDetails<int>> ResolveIntegerValue(string flagKey, int defaultValue,
EvaluationContext context = null);
public abstract Task<ResolutionDetails<int>> ResolveIntegerValueAsync(string flagKey, int defaultValue,
EvaluationContext context = null, CancellationToken cancellationToken = default);

/// <summary>
/// Resolves a double feature flag
/// </summary>
/// <param name="flagKey">Feature flag key</param>
/// <param name="defaultValue">Default value</param>
/// <param name="context"><see cref="EvaluationContext"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns><see cref="ResolutionDetails{T}"/></returns>
public abstract Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKey, double defaultValue,
EvaluationContext context = null);
public abstract Task<ResolutionDetails<double>> ResolveDoubleValueAsync(string flagKey, double defaultValue,
EvaluationContext context = null, CancellationToken cancellationToken = default);

/// <summary>
/// Resolves a structured feature flag
/// </summary>
/// <param name="flagKey">Feature flag key</param>
/// <param name="defaultValue">Default value</param>
/// <param name="context"><see cref="EvaluationContext"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns><see cref="ResolutionDetails{T}"/></returns>
public abstract Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue,
EvaluationContext context = null);
public abstract Task<ResolutionDetails<Value>> ResolveStructureValueAsync(string flagKey, Value defaultValue,
EvaluationContext context = null, CancellationToken cancellationToken = default);

/// <summary>
/// Get the status of the provider.
Expand All @@ -95,7 +101,7 @@ public abstract Task<ResolutionDetails<Value>> ResolveStructureValue(string flag
/// If a provider does not override this method, then its status will be assumed to be
/// <see cref="ProviderStatus.Ready"/>. If a provider implements this method, and supports initialization,
/// then it should start in the <see cref="ProviderStatus.NotReady"/>status . If the status is
/// <see cref="ProviderStatus.NotReady"/>, then the Api will call the <see cref="Initialize" /> when the
/// <see cref="ProviderStatus.NotReady"/>, then the Api will call the <see cref="InitializeAsync" /> when the
/// provider is set.
/// </remarks>
public virtual ProviderStatus GetStatus() => ProviderStatus.Ready;
Expand All @@ -107,6 +113,7 @@ public abstract Task<ResolutionDetails<Value>> ResolveStructureValue(string flag
/// </para>
/// </summary>
/// <param name="context"><see cref="EvaluationContext"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>A task that completes when the initialization process is complete.</returns>
/// <remarks>
/// <para>
Expand All @@ -118,7 +125,7 @@ public abstract Task<ResolutionDetails<Value>> ResolveStructureValue(string flag
/// the <see cref="GetStatus"/> method after initialization is complete.
/// </para>
/// </remarks>
public virtual Task Initialize(EvaluationContext context)
public virtual Task InitializeAsync(EvaluationContext context, CancellationToken cancellationToken = default)
{
// Intentionally left blank.
return Task.CompletedTask;
Expand All @@ -129,7 +136,8 @@ public virtual Task Initialize(EvaluationContext context)
/// Providers can overwrite this method, if they have special shutdown actions needed.
/// </summary>
/// <returns>A task that completes when the shutdown process is complete.</returns>
public virtual Task Shutdown()
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
public virtual Task ShutdownAsync(CancellationToken cancellationToken = default)
{
// Intentionally left blank.
return Task.CompletedTask;
Expand Down
Loading

0 comments on commit ef28ff5

Please sign in to comment.