diff --git a/README.md b/README.md index 562928fd..6b11ce74 100644 --- a/README.md +++ b/README.md @@ -295,25 +295,25 @@ To satisfy the interface, all methods (`BeforeAsync`/`AfterAsync`/`FinallyAsync` ```csharp public class MyHook : Hook { - public Task BeforeAsync(HookContext context, + public ValueTask BeforeAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { // code to run before flag evaluation } - public virtual Task AfterAsync(HookContext context, FlagEvaluationDetails details, + public virtual ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { // code to run after successful flag evaluation } - public virtual Task ErrorAsync(HookContext context, Exception error, + public virtual ValueTask ErrorAsync(HookContext context, Exception error, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { // code to run if there's an error during before hooks or during flag evaluation } - public virtual Task FinallyAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) + public virtual ValueTask FinallyAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { // code to run after all other stages, regardless of success/failure } diff --git a/src/OpenFeature/Api.cs b/src/OpenFeature/Api.cs index ef9bf5fe..1e9f2f6b 100644 --- a/src/OpenFeature/Api.cs +++ b/src/OpenFeature/Api.cs @@ -44,7 +44,7 @@ private Api() { } /// The provider cannot be set to null. Attempting to set the provider to null has no effect. /// Implementation of /// The . - public async Task SetProviderAsync(FeatureProvider featureProvider, CancellationToken cancellationToken = default) + public async ValueTask SetProviderAsync(FeatureProvider featureProvider, CancellationToken cancellationToken = default) { this.EventExecutor.RegisterDefaultFeatureProvider(featureProvider); await this._repository.SetProviderAsync(featureProvider, this.GetContext(), cancellationToken: cancellationToken).ConfigureAwait(false); @@ -58,7 +58,7 @@ public async Task SetProviderAsync(FeatureProvider featureProvider, Cancellation /// Name of client /// Implementation of /// The . - public async Task SetProviderAsync(string clientName, FeatureProvider featureProvider, CancellationToken cancellationToken = default) + public async ValueTask SetProviderAsync(string clientName, FeatureProvider featureProvider, CancellationToken cancellationToken = default) { this.EventExecutor.RegisterClientFeatureProvider(clientName, featureProvider); await this._repository.SetProviderAsync(clientName, featureProvider, this.GetContext(), cancellationToken: cancellationToken).ConfigureAwait(false); @@ -206,7 +206,7 @@ public EvaluationContext GetContext() /// /// /// The . - public async Task ShutdownAsync(CancellationToken cancellationToken = default) + public async ValueTask ShutdownAsync(CancellationToken cancellationToken = default) { await this._repository.ShutdownAsync(cancellationToken: cancellationToken).ConfigureAwait(false); await this.EventExecutor.ShutdownAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/OpenFeature/EventExecutor.cs b/src/OpenFeature/EventExecutor.cs index aa6ac71d..212e0908 100644 --- a/src/OpenFeature/EventExecutor.cs +++ b/src/OpenFeature/EventExecutor.cs @@ -11,7 +11,7 @@ namespace OpenFeature { - internal delegate Task ShutdownDelegate(CancellationToken cancellationToken); + internal delegate ValueTask ShutdownDelegate(CancellationToken cancellationToken); internal class EventExecutor { @@ -327,7 +327,7 @@ private void InvokeEventHandler(EventHandlerDelegate eventHandler, Event e) } } - public async Task ShutdownAsync(CancellationToken cancellationToken = default) + public async ValueTask ShutdownAsync(CancellationToken cancellationToken = default) { await this._shutdownDelegate(cancellationToken).ConfigureAwait(false); } @@ -338,7 +338,7 @@ internal void SetShutdownDelegate(ShutdownDelegate del) } // Method to signal shutdown - private async Task SignalShutdownAsync(CancellationToken cancellationToken) + private async ValueTask SignalShutdownAsync(CancellationToken cancellationToken) { // Enqueue a shutdown signal await this.EventChannel.Writer.WriteAsync(new ShutdownSignal(), cancellationToken).ConfigureAwait(false); diff --git a/src/OpenFeature/FeatureProvider.cs b/src/OpenFeature/FeatureProvider.cs index 3fad2399..30318e81 100644 --- a/src/OpenFeature/FeatureProvider.cs +++ b/src/OpenFeature/FeatureProvider.cs @@ -125,10 +125,10 @@ public abstract Task> ResolveStructureValueAsync(string /// the method after initialization is complete. /// /// - public virtual Task InitializeAsync(EvaluationContext context, CancellationToken cancellationToken = default) + public virtual ValueTask InitializeAsync(EvaluationContext context, CancellationToken cancellationToken = default) { // Intentionally left blank. - return Task.CompletedTask; + return new ValueTask(); } /// @@ -137,10 +137,10 @@ public virtual Task InitializeAsync(EvaluationContext context, CancellationToken /// /// A task that completes when the shutdown process is complete. /// The . - public virtual Task ShutdownAsync(CancellationToken cancellationToken = default) + public virtual ValueTask ShutdownAsync(CancellationToken cancellationToken = default) { // Intentionally left blank. - return Task.CompletedTask; + return new ValueTask(); } /// diff --git a/src/OpenFeature/Hook.cs b/src/OpenFeature/Hook.cs index 95964b11..abf522ee 100644 --- a/src/OpenFeature/Hook.cs +++ b/src/OpenFeature/Hook.cs @@ -30,10 +30,10 @@ public abstract class Hook /// The . /// Flag value type (bool|number|string|object) /// Modified EvaluationContext that is used for the flag evaluation - public virtual Task BeforeAsync(HookContext context, + public virtual ValueTask BeforeAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { - return Task.FromResult(EvaluationContext.Empty); + return new ValueTask(EvaluationContext.Empty); } /// @@ -44,10 +44,10 @@ public virtual Task BeforeAsync(HookContext context, /// Caller provided data /// The . /// Flag value type (bool|number|string|object) - public virtual Task AfterAsync(HookContext context, FlagEvaluationDetails details, + public virtual ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { - return Task.CompletedTask; + return new ValueTask(); } /// @@ -58,10 +58,10 @@ public virtual Task AfterAsync(HookContext context, FlagEvaluationDetails< /// Caller provided data /// The . /// Flag value type (bool|number|string|object) - public virtual Task ErrorAsync(HookContext context, Exception error, + public virtual ValueTask ErrorAsync(HookContext context, Exception error, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { - return Task.CompletedTask; + return new ValueTask(); } /// @@ -71,9 +71,9 @@ public virtual Task ErrorAsync(HookContext context, Exception error, /// Caller provided data /// The . /// Flag value type (bool|number|string|object) - public virtual Task FinallyAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) + public virtual ValueTask FinallyAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { - return Task.CompletedTask; + return new ValueTask(); } } } diff --git a/src/OpenFeature/OpenFeatureClient.cs b/src/OpenFeature/OpenFeatureClient.cs index f258b858..c657b2fa 100644 --- a/src/OpenFeature/OpenFeatureClient.cs +++ b/src/OpenFeature/OpenFeatureClient.cs @@ -256,7 +256,7 @@ private async Task> EvaluateFlagAsync( return evaluation; } - private async Task> TriggerBeforeHooksAsync(IReadOnlyList hooks, HookContext context, + private async ValueTask> TriggerBeforeHooksAsync(IReadOnlyList hooks, HookContext context, FlagEvaluationOptions options, CancellationToken cancellationToken = default) { var evalContextBuilder = EvaluationContext.Builder(); @@ -280,7 +280,7 @@ private async Task> TriggerBeforeHooksAsync(IReadOnlyList(IReadOnlyList hooks, HookContext context, + private async ValueTask TriggerAfterHooksAsync(IReadOnlyList hooks, HookContext context, FlagEvaluationDetails evaluationDetails, FlagEvaluationOptions options, CancellationToken cancellationToken = default) { foreach (var hook in hooks) @@ -289,7 +289,7 @@ private async Task TriggerAfterHooksAsync(IReadOnlyList hooks, HookCont } } - private async Task TriggerErrorHooksAsync(IReadOnlyList hooks, HookContext context, Exception exception, + private async ValueTask TriggerErrorHooksAsync(IReadOnlyList hooks, HookContext context, Exception exception, FlagEvaluationOptions options, CancellationToken cancellationToken = default) { foreach (var hook in hooks) @@ -305,7 +305,7 @@ private async Task TriggerErrorHooksAsync(IReadOnlyList hooks, HookCont } } - private async Task TriggerFinallyHooksAsync(IReadOnlyList hooks, HookContext context, + private async ValueTask TriggerFinallyHooksAsync(IReadOnlyList hooks, HookContext context, FlagEvaluationOptions options, CancellationToken cancellationToken = default) { foreach (var hook in hooks) diff --git a/src/OpenFeature/ProviderRepository.cs b/src/OpenFeature/ProviderRepository.cs index 176de2fa..515a7300 100644 --- a/src/OpenFeature/ProviderRepository.cs +++ b/src/OpenFeature/ProviderRepository.cs @@ -55,7 +55,7 @@ internal class ProviderRepository /// /// called after a provider is shutdown, can be used to remove event handlers /// The . - public async Task SetProviderAsync( + public async ValueTask SetProviderAsync( FeatureProvider featureProvider, EvaluationContext context, Action afterSet = null, @@ -98,7 +98,7 @@ await InitProviderAsync(this._defaultProvider, context, afterInitialization, aft .ConfigureAwait(false); } - private static async Task InitProviderAsync( + private static async ValueTask InitProviderAsync( FeatureProvider newProvider, EvaluationContext context, Action afterInitialization, @@ -148,7 +148,7 @@ private static async Task InitProviderAsync( /// /// called after a provider is shutdown, can be used to remove event handlers /// The . - public async Task SetProviderAsync(string clientName, + public async ValueTask SetProviderAsync(string clientName, FeatureProvider featureProvider, EvaluationContext context, Action afterSet = null, @@ -198,7 +198,7 @@ public async Task SetProviderAsync(string clientName, /// /// Shutdown the feature provider if it is unused. This must be called within a write lock of the _providersLock. /// - private async Task ShutdownIfUnusedAsync( + private async ValueTask ShutdownIfUnusedAsync( FeatureProvider targetProvider, Action afterShutdown, Action afterError, @@ -226,7 +226,7 @@ private async Task ShutdownIfUnusedAsync( /// it would not be meaningful to emit an error. /// /// - private static async Task SafeShutdownProviderAsync(FeatureProvider targetProvider, + private static async ValueTask SafeShutdownProviderAsync(FeatureProvider targetProvider, Action afterShutdown, Action afterError, CancellationToken cancellationToken) @@ -267,7 +267,7 @@ public FeatureProvider GetProvider(string clientName) : this.GetProvider(); } - public async Task ShutdownAsync(Action afterError = null, CancellationToken cancellationToken = default) + public async ValueTask ShutdownAsync(Action afterError = null, CancellationToken cancellationToken = default) { var providers = new HashSet(); this._providersLock.EnterWriteLock(); diff --git a/test/OpenFeature.E2ETests/Steps/EvaluationStepDefinitions.cs b/test/OpenFeature.E2ETests/Steps/EvaluationStepDefinitions.cs index 745e7e66..1061a7dc 100644 --- a/test/OpenFeature.E2ETests/Steps/EvaluationStepDefinitions.cs +++ b/test/OpenFeature.E2ETests/Steps/EvaluationStepDefinitions.cs @@ -42,7 +42,7 @@ public EvaluationStepDefinitions(ScenarioContext scenarioContext) { _scenarioContext = scenarioContext; var flagdProvider = new FlagdProvider(); - Api.Instance.SetProviderAsync(flagdProvider).Wait(); + Api.Instance.SetProviderAsync(flagdProvider).GetAwaiter().GetResult(); client = Api.Instance.GetClient(); } diff --git a/test/OpenFeature.Tests/ClearOpenFeatureInstanceFixture.cs b/test/OpenFeature.Tests/ClearOpenFeatureInstanceFixture.cs index 5f31c71a..30328e1c 100644 --- a/test/OpenFeature.Tests/ClearOpenFeatureInstanceFixture.cs +++ b/test/OpenFeature.Tests/ClearOpenFeatureInstanceFixture.cs @@ -7,7 +7,7 @@ public ClearOpenFeatureInstanceFixture() { Api.Instance.SetContext(null); Api.Instance.ClearHooks(); - Api.Instance.SetProviderAsync(new NoOpFeatureProvider()).Wait(); + Api.Instance.SetProviderAsync(new NoOpFeatureProvider()).GetAwaiter().GetResult(); } } } diff --git a/test/OpenFeature.Tests/OpenFeatureHookTests.cs b/test/OpenFeature.Tests/OpenFeatureHookTests.cs index 8c624a1d..695f7830 100644 --- a/test/OpenFeature.Tests/OpenFeatureHookTests.cs +++ b/test/OpenFeature.Tests/OpenFeatureHookTests.cs @@ -39,14 +39,14 @@ public async Task Hooks_Should_Be_Called_In_Order() clientHook.BeforeAsync(Arg.Any>(), Arg.Any>()).Returns(EvaluationContext.Empty); invocationHook.BeforeAsync(Arg.Any>(), Arg.Any>()).Returns(EvaluationContext.Empty); providerHook.BeforeAsync(Arg.Any>(), Arg.Any>()).Returns(EvaluationContext.Empty); - providerHook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>()).Returns(Task.CompletedTask); - invocationHook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>()).Returns(Task.CompletedTask); - clientHook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>()).Returns(Task.CompletedTask); - apiHook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>()).Returns(Task.CompletedTask); - providerHook.FinallyAsync(Arg.Any>(), Arg.Any>()).Returns(Task.CompletedTask); - invocationHook.FinallyAsync(Arg.Any>(), Arg.Any>()).Returns(Task.CompletedTask); - clientHook.FinallyAsync(Arg.Any>(), Arg.Any>()).Returns(Task.CompletedTask); - apiHook.FinallyAsync(Arg.Any>(), Arg.Any>()).Returns(Task.CompletedTask); + providerHook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>()).Returns(new ValueTask()); + invocationHook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>()).Returns(new ValueTask()); + clientHook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>()).Returns(new ValueTask()); + apiHook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>()).Returns(new ValueTask()); + providerHook.FinallyAsync(Arg.Any>(), Arg.Any>()).Returns(new ValueTask()); + invocationHook.FinallyAsync(Arg.Any>(), Arg.Any>()).Returns(new ValueTask()); + clientHook.FinallyAsync(Arg.Any>(), Arg.Any>()).Returns(new ValueTask()); + apiHook.FinallyAsync(Arg.Any>(), Arg.Any>()).Returns(new ValueTask()); var testProvider = new TestProvider(); testProvider.AddHook(providerHook); @@ -327,9 +327,9 @@ public async Task Finally_Hook_Should_Be_Executed_Even_If_Abnormal_Termination() hook1.BeforeAsync(Arg.Any>(), null).Returns(EvaluationContext.Empty); hook2.BeforeAsync(Arg.Any>(), null).Returns(EvaluationContext.Empty); featureProvider.ResolveBooleanValueAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(new ResolutionDetails("test", false)); - hook2.AfterAsync(Arg.Any>(), Arg.Any>(), null).Returns(Task.CompletedTask); - hook1.AfterAsync(Arg.Any>(), Arg.Any>(), null).Returns(Task.CompletedTask); - hook2.FinallyAsync(Arg.Any>(), null).Returns(Task.CompletedTask); + hook2.AfterAsync(Arg.Any>(), Arg.Any>(), null).Returns(new ValueTask()); + hook1.AfterAsync(Arg.Any>(), Arg.Any>(), null).Returns(new ValueTask()); + hook2.FinallyAsync(Arg.Any>(), null).Returns(new ValueTask()); hook1.FinallyAsync(Arg.Any>(), null).Throws(new Exception()); await Api.Instance.SetProviderAsync(featureProvider); @@ -374,8 +374,8 @@ public async Task Error_Hook_Should_Be_Executed_Even_If_Abnormal_Termination() hook1.BeforeAsync(Arg.Any>(), null).Returns(EvaluationContext.Empty); hook2.BeforeAsync(Arg.Any>(), null).Returns(EvaluationContext.Empty); featureProvider1.ResolveBooleanValueAsync(Arg.Any(), Arg.Any(), Arg.Any()).Throws(new Exception()); - hook2.ErrorAsync(Arg.Any>(), Arg.Any(), null).Returns(Task.CompletedTask); - hook1.ErrorAsync(Arg.Any>(), Arg.Any(), null).Returns(Task.CompletedTask); + hook2.ErrorAsync(Arg.Any>(), Arg.Any(), null).Returns(new ValueTask()); + hook1.ErrorAsync(Arg.Any>(), Arg.Any(), null).Returns(new ValueTask()); await Api.Instance.SetProviderAsync(featureProvider1); var client = Api.Instance.GetClient(); @@ -410,7 +410,7 @@ public async Task Error_Occurs_During_Before_After_Evaluation_Should_Not_Invoke_ featureProvider.GetProviderHooks().Returns(ImmutableList.Empty); // Sequence - hook1.BeforeAsync(Arg.Any>(), Arg.Any>()).ThrowsAsync(new Exception()); + hook1.BeforeAsync(Arg.Any>(), Arg.Any>()).AsTask().ThrowsAsync(new Exception()); _ = hook1.ErrorAsync(Arg.Any>(), Arg.Any(), null); _ = hook2.ErrorAsync(Arg.Any>(), Arg.Any(), null); @@ -454,10 +454,10 @@ public async Task Hook_Hints_May_Be_Optional() .Returns(new ResolutionDetails("test", false)); hook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>()) - .Returns(Task.FromResult(Task.CompletedTask)); + .Returns(new ValueTask()); hook.FinallyAsync(Arg.Any>(), Arg.Any>()) - .Returns(Task.CompletedTask); + .Returns(new ValueTask()); await Api.Instance.SetProviderAsync(featureProvider); var client = Api.Instance.GetClient(); @@ -485,9 +485,9 @@ public async Task When_Error_Occurs_In_Before_Hook_Should_Return_Default_Value() featureProvider.GetMetadata().Returns(new Metadata(null)); // Sequence - hook.BeforeAsync(Arg.Any>(), Arg.Any>()).ThrowsAsync(exceptionToThrow); - hook.ErrorAsync(Arg.Any>(), Arg.Any(), null).Returns(Task.CompletedTask); - hook.FinallyAsync(Arg.Any>(), null).Returns(Task.CompletedTask); + hook.BeforeAsync(Arg.Any>(), Arg.Any>()).AsTask().ThrowsAsync(exceptionToThrow); + hook.ErrorAsync(Arg.Any>(), Arg.Any(), null).Returns(new ValueTask()); + hook.FinallyAsync(Arg.Any>(), null).Returns(new ValueTask()); var client = Api.Instance.GetClient(); client.AddHooks(hook); @@ -529,13 +529,14 @@ public async Task When_Error_Occurs_In_After_Hook_Should_Invoke_Error_Hook() .Returns(new ResolutionDetails("test", false)); hook.AfterAsync(Arg.Any>(), Arg.Any>(), Arg.Any>()) + .AsTask() .ThrowsAsync(exceptionToThrow); hook.ErrorAsync(Arg.Any>(), Arg.Any(), Arg.Any>()) - .Returns(Task.CompletedTask); + .Returns(new ValueTask()); hook.FinallyAsync(Arg.Any>(), Arg.Any>()) - .Returns(Task.CompletedTask); + .Returns(new ValueTask()); await Api.Instance.SetProviderAsync(featureProvider); var client = Api.Instance.GetClient(); diff --git a/test/OpenFeature.Tests/OpenFeatureTests.cs b/test/OpenFeature.Tests/OpenFeatureTests.cs index 0d93a6d9..9a38daaa 100644 --- a/test/OpenFeature.Tests/OpenFeatureTests.cs +++ b/test/OpenFeature.Tests/OpenFeatureTests.cs @@ -12,10 +12,7 @@ namespace OpenFeature.Tests { public class OpenFeatureTests : ClearOpenFeatureInstanceFixture { - static async Task EmptyShutdown(CancellationToken cancellationToken) - { - await Task.FromResult(0).ConfigureAwait(false); - } + static ValueTask EmptyShutdown(CancellationToken cancellationToken) => new ValueTask(); [Fact] [Specification("1.1.1", "The `API`, and any state it maintains SHOULD exist as a global singleton, even in cases wherein multiple versions of the `API` are present at runtime.")] @@ -189,7 +186,7 @@ public void OpenFeature_Should_Add_Hooks() [Specification("1.1.5", "The API MUST provide a function for retrieving the metadata field of the configured `provider`.")] public void OpenFeature_Should_Get_Metadata() { - Api.Instance.SetProviderAsync(new NoOpFeatureProvider()).Wait(); + Api.Instance.SetProviderAsync(new NoOpFeatureProvider()).GetAwaiter().GetResult(); var openFeature = Api.Instance; var metadata = openFeature.GetProviderMetadata(); diff --git a/test/OpenFeature.Tests/TestImplementations.cs b/test/OpenFeature.Tests/TestImplementations.cs index a48f884b..0bda65f4 100644 --- a/test/OpenFeature.Tests/TestImplementations.cs +++ b/test/OpenFeature.Tests/TestImplementations.cs @@ -12,25 +12,25 @@ public class TestHookNoOverride : Hook { } public class TestHook : Hook { - public override Task BeforeAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) + public override ValueTask BeforeAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { - return Task.FromResult(EvaluationContext.Empty); + return new ValueTask(EvaluationContext.Empty); } - public override Task AfterAsync(HookContext context, FlagEvaluationDetails details, + public override ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { - return Task.CompletedTask; + return new ValueTask(); } - public override Task ErrorAsync(HookContext context, Exception error, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) + public override ValueTask ErrorAsync(HookContext context, Exception error, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { - return Task.CompletedTask; + return new ValueTask(); } - public override Task FinallyAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) + public override ValueTask FinallyAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default) { - return Task.CompletedTask; + return new ValueTask(); } } @@ -105,7 +105,7 @@ public void SetStatus(ProviderStatus status) this._status = status; } - public override async Task InitializeAsync(EvaluationContext context, CancellationToken cancellationToken = default) + public override async ValueTask InitializeAsync(EvaluationContext context, CancellationToken cancellationToken = default) { this._status = ProviderStatus.Ready; await this.EventChannel.Writer.WriteAsync(new ProviderEventPayload { Type = ProviderEventTypes.ProviderReady, ProviderName = this.GetMetadata().Name }, cancellationToken).ConfigureAwait(false);