Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Use ValueTask on plumbing methods #185

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 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()));
```

### Logging
Expand Down Expand Up @@ -224,7 +224,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 @@ -235,7 +235,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 @@ -254,27 +254,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 @@ -286,30 +286,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 ValueTask<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 ValueTask 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 ValueTask 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 ValueTask 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 ValueTask 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 ValueTask 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 @@ -222,10 +224,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 ValueTask 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 ValueTask 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 ValueTask 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 ValueTask 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
38 changes: 23 additions & 15 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,21 +125,22 @@ 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 ValueTask InitializeAsync(EvaluationContext context, CancellationToken cancellationToken = default)
{
// Intentionally left blank.
return Task.CompletedTask;
return new ValueTask();
}

/// <summary>
/// This method is called when a new provider is about to be used to evaluate flags, or the SDK is shut down.
/// 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 ValueTask ShutdownAsync(CancellationToken cancellationToken = default)
{
// Intentionally left blank.
return Task.CompletedTask;
return new ValueTask();
}

/// <summary>
Expand Down
Loading
Loading