From 77666f84ebfcb08986ff6ff46d88dc2fd024cd65 Mon Sep 17 00:00:00 2001 From: Jack Mott Date: Thu, 27 Feb 2025 12:33:41 -0600 Subject: [PATCH 1/7] poc of an approach to adding async --- pkgs/sdk/server/src/Interfaces/ILdClient.cs | 23 +++ .../Internal/DataStores/InMemoryDataStore.cs | 15 ++ .../DataStores/PersistentStoreAsyncAdapter.cs | 7 +- .../DataStores/PersistentStoreWrapper.cs | 30 +++- .../src/Internal/Hooks/Executor/Executor.cs | 12 ++ .../Internal/Hooks/Executor/NoopExecutor.cs | 5 + .../Hooks/Interfaces/IHookExecutor.cs | 5 + pkgs/sdk/server/src/LdClient.cs | 131 ++++++++++++++++++ pkgs/sdk/server/src/Subsystems/IDataStore.cs | 7 + .../src/Subsystems/IPersistentDataStore.cs | 8 +- .../Subsystems/IPersistentDataStoreAsync.cs | 6 +- .../DataStoreStatusProviderImplTest.cs | 5 + .../PersistentStoreWrapperTestAsync.cs | 3 +- .../PersistentStoreWrapperTestBase.cs | 19 +++ 14 files changed, 270 insertions(+), 6 deletions(-) diff --git a/pkgs/sdk/server/src/Interfaces/ILdClient.cs b/pkgs/sdk/server/src/Interfaces/ILdClient.cs index 928fb7ca..976b3145 100644 --- a/pkgs/sdk/server/src/Interfaces/ILdClient.cs +++ b/pkgs/sdk/server/src/Interfaces/ILdClient.cs @@ -1,6 +1,8 @@ using System; using LaunchDarkly.Sdk.Server.Migrations; using LaunchDarkly.Logging; +using System.Threading.Tasks; +using System.Threading; namespace LaunchDarkly.Sdk.Server.Interfaces { @@ -96,6 +98,27 @@ public interface ILdClient /// EvaluationDetail BoolVariationDetail(string key, Context context, bool defaultValue); + /// + /// Calculates the boolean value of a feature flag for a given context, and returns an object that + /// describes the way the value was determined. Async + /// + /// + /// + /// The property in the result will also be included + /// in analytics events, if you are capturing detailed event data for this flag. + /// + /// + /// The behavior is otherwise identical to . + /// + /// + /// the unique feature key for the feature flag + /// the evaluation context + /// the default value of the flag + /// optional cancelation token + /// an object + /// + Task> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancelationToken = default); + /// /// Calculates the integer value of a feature flag for a given context. /// diff --git a/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs b/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs index ebc86be0..7518cdf6 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs @@ -1,4 +1,6 @@ using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; using LaunchDarkly.Sdk.Server.Subsystems; using static LaunchDarkly.Sdk.Server.Subsystems.DataStoreTypes; @@ -60,6 +62,19 @@ public void Init(FullDataSet data) return item; } + public Task GetAsync(DataKind kind, string key, CancellationToken cancelationToken = default) + { + if (!Items.TryGetValue(kind, out var itemsOfKind)) + { + return null; + } + if (!itemsOfKind.TryGetValue(key, out var item)) + { + return null; + } + return Task.FromResult(item); + } + public KeyedItems GetAll(DataKind kind) { if (Items.TryGetValue(kind, out var itemsOfKind)) diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs index 915fdb1f..ee1b706d 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs @@ -33,7 +33,12 @@ public void Init(FullDataSet allData) { return WaitSafely(() => _coreAsync.GetAsync(kind, key)); } - + + public Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + { + return _coreAsync.GetAsync(kind, key,cancellationToken); + } + public KeyedItems GetAll(DataKind kind) { return WaitSafely(() => _coreAsync.GetAllAsync(kind)); diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs index 2af8189d..e4e2c59f 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using LaunchDarkly.Cache; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; @@ -185,6 +187,22 @@ public void Init(FullDataSet items) } } + public async Task GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default) + { + try + { + var ret = _itemCache is null ? await GetAndDeserializeItemAsync(kind, key) : + _itemCache.Get(new CacheKey(kind, key)); + ProcessError(null); + return ret; + } + catch (Exception e) + { + ProcessError(e); + throw; + } + } + public KeyedItems GetAll(DataKind kind) { try @@ -319,7 +337,17 @@ private void Dispose(bool disposing) } return Deserialize(kind, maybeSerializedItem.Value); } - + + private async Task GetAndDeserializeItemAsync(DataKind kind, string key) + { + var maybeSerializedItem = await _core.GetAsync(kind, key); + if (!maybeSerializedItem.HasValue) + { + return null; + } + return Deserialize(kind, maybeSerializedItem.Value); + } + private ImmutableDictionary GetAllAndDeserialize(DataKind kind) { return _core.GetAll(kind).Items.ToImmutableDictionary( diff --git a/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs b/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs index 7dfc6500..82acd1e7 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs @@ -8,6 +8,8 @@ using LaunchDarkly.Sdk.Server.Internal.Hooks.Series; using LaunchDarkly.Sdk.Server.Internal.Hooks.Interfaces; using LaunchDarkly.Sdk.Server.Internal.Model; +using System.Threading.Tasks; +using System.Threading; namespace LaunchDarkly.Sdk.Server.Internal.Hooks.Executor { @@ -36,6 +38,16 @@ public Executor(Logger logger, IEnumerable hooks) return (detail, flag); } + public async Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) + { + var seriesData = _beforeEvaluation.Execute(context, default); + + var (detail, flag) = await evaluateAsync(); + + _afterEvaluation.Execute(context, new EvaluationDetail(converter.FromType(detail.Value), detail.VariationIndex, detail.Reason), seriesData); + return (detail, flag); + } + public void Dispose() { foreach (var hook in _hooks) diff --git a/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs b/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs index 4d65a720..3aba0419 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using LaunchDarkly.Sdk.Server.Hooks; using LaunchDarkly.Sdk.Server.Internal.Hooks.Interfaces; using LaunchDarkly.Sdk.Server.Internal.Model; @@ -14,6 +16,9 @@ internal sealed class NoopExecutor: IHookExecutor public (EvaluationDetail, FeatureFlag) EvaluationSeries(EvaluationSeriesContext context, LdValue.Converter converter, Func<(EvaluationDetail, FeatureFlag)> evaluate) => evaluate(); + public Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, + LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) => evaluateAsync(); + public void Dispose() { } diff --git a/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs b/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs index 83913c7f..0ce97d09 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using LaunchDarkly.Sdk.Server.Hooks; using LaunchDarkly.Sdk.Server.Internal.Model; @@ -25,5 +27,8 @@ internal interface IHookExecutor: IDisposable /// the EvaluationDetail returned from the evaluator (EvaluationDetail, FeatureFlag) EvaluationSeries(EvaluationSeriesContext context, LdValue.Converter converter, Func<(EvaluationDetail, FeatureFlag)> evaluate); + + Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, + Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default); } } diff --git a/pkgs/sdk/server/src/LdClient.cs b/pkgs/sdk/server/src/LdClient.cs index ecc32f28..8803a6da 100644 --- a/pkgs/sdk/server/src/LdClient.cs +++ b/pkgs/sdk/server/src/LdClient.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Server.Hooks; @@ -296,6 +298,10 @@ public LdValue JsonVariation(string key, Context context, LdValue defaultValue) public EvaluationDetail BoolVariationDetail(string key, Context context, bool defaultValue) => Evaluate(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons); + /// + public Task> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancellationToken = default) => + EvaluateAsync(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons); + /// public EvaluationDetail IntVariationDetail(string key, Context context, int defaultValue) => Evaluate(Method.IntVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Int, true, EventFactory.DefaultWithReasons); @@ -435,6 +441,16 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ () => EvaluationAndFlag(key, context, defaultValue, converter, checkType, eventFactory)); } + private async Task<(EvaluationDetail, FeatureFlag)> EvaluateWithHooksAsync(string method, string key, Context context, LdValue defaultValue, LdValue.Converter converter, + bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) + { + var evalSeriesContext = new EvaluationSeriesContext(key, context, defaultValue, method); + return await _hookExecutor.EvaluationSeriesAsync( + evalSeriesContext, + converter, + () => EvaluationAndFlagAsync(key, context, defaultValue, converter, checkType, eventFactory,cancellationToken)); + } + private (EvaluationDetail, FeatureFlag) EvaluationAndFlag(string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory) @@ -533,12 +549,117 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ } } + private async Task<(EvaluationDetail, FeatureFlag)> EvaluationAndFlagAsync(string featureKey, Context context, + LdValue defaultValue, LdValue.Converter converter, + bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) + { + T defaultValueOfType = converter.ToType(defaultValue); + if (!Initialized) + { + // TODO should this also be async? + if (_dataStore.Initialized()) + { + _evalLog.Warn("Flag evaluation before client initialized; using last known values from data store"); + } + else + { + _evalLog.Warn("Flag evaluation before client initialized; data store unavailable, returning default value"); + return (new EvaluationDetail(defaultValueOfType, null, + EvaluationReason.ErrorReason(EvaluationErrorKind.ClientNotReady)), null); + } + } + + if (!context.Valid) + { + _evalLog.Warn("Invalid evaluation context when evaluating flag \"{0}\" ({1}); returning default value", featureKey, + context.Error); + return (new EvaluationDetail(defaultValueOfType, null, + EvaluationReason.ErrorReason(EvaluationErrorKind.UserNotSpecified)), null); + } + + FeatureFlag featureFlag = null; + try + { + featureFlag = await GetFlagAsync(featureKey,cancellationToken); + if (featureFlag == null) + { + _evalLog.Info("Unknown feature flag \"{0}\"; returning default value", + featureKey); + _eventProcessor.RecordEvaluationEvent(eventFactory.NewUnknownFlagEvaluationEvent( + featureKey, context, defaultValue, EvaluationErrorKind.FlagNotFound)); + return (new EvaluationDetail(defaultValueOfType, null, + EvaluationReason.ErrorReason(EvaluationErrorKind.FlagNotFound)), null); + } + + EvaluatorTypes.EvalResult evalResult = _evaluator.Evaluate(featureFlag, context); + if (!IsOffline()) + { + foreach (var prereqEvent in evalResult.PrerequisiteEvals) + { + _eventProcessor.RecordEvaluationEvent(eventFactory.NewPrerequisiteEvaluationEvent( + prereqEvent.PrerequisiteFlag, context, prereqEvent.Result, prereqEvent.FlagKey)); + } + } + var evalDetail = evalResult.Result; + EvaluationDetail returnDetail; + if (evalDetail.VariationIndex == null) + { + returnDetail = new EvaluationDetail(defaultValueOfType, null, evalDetail.Reason); + evalDetail = new EvaluationDetail(defaultValue, null, evalDetail.Reason); + } + else + { + if (checkType && !defaultValue.IsNull && evalDetail.Value.Type != defaultValue.Type) + { + _evalLog.Error("Expected type {0} but got {1} when evaluating feature flag \"{2}\"; returning default value", + defaultValue.Type, + evalDetail.Value.Type, + featureKey); + + _eventProcessor.RecordEvaluationEvent(eventFactory.NewDefaultValueEvaluationEvent( + featureFlag, context, defaultValue, EvaluationErrorKind.WrongType)); + return (new EvaluationDetail(defaultValueOfType, null, + EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)), featureFlag); + } + returnDetail = new EvaluationDetail(converter.ToType(evalDetail.Value), + evalDetail.VariationIndex, evalDetail.Reason); + } + _eventProcessor.RecordEvaluationEvent(eventFactory.NewEvaluationEvent( + featureFlag, context, evalDetail, defaultValue)); + return (returnDetail, featureFlag); + } + catch (Exception e) + { + LogHelpers.LogException(_evalLog, + string.Format("Exception when evaluating feature flag \"{0}\"", featureKey), + e); + var reason = EvaluationReason.ErrorReason(EvaluationErrorKind.Exception); + if (featureFlag == null) + { + _eventProcessor.RecordEvaluationEvent(eventFactory.NewUnknownFlagEvaluationEvent( + featureKey, context, defaultValue, EvaluationErrorKind.Exception)); + } + else + { + _eventProcessor.RecordEvaluationEvent(eventFactory.NewEvaluationEvent( + featureFlag, context, new EvaluationDetail(defaultValue, null, reason), defaultValue)); + } + return (new EvaluationDetail(defaultValueOfType, null, reason), null); + } + } + private EvaluationDetail Evaluate(string method, string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory) { return EvaluateWithHooks(method, featureKey, context, defaultValue, converter, checkType, eventFactory).Item1; } + private async Task> EvaluateAsync(string method, string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, + bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) + { + return (await EvaluateWithHooksAsync(method, featureKey, context, defaultValue, converter, checkType, eventFactory,cancellationToken)).Item1; + } + /// public string SecureModeHash(Context context) { @@ -658,6 +779,16 @@ private FeatureFlag GetFlag(string key) return null; } + private async Task GetFlagAsync(string key, CancellationToken cancellationToken = default) + { + var maybeItem = await _dataStore.GetAsync(DataModel.Features, key,cancellationToken); + if (maybeItem.HasValue && maybeItem.Value.Item != null && maybeItem.Value.Item is FeatureFlag f) + { + return f; + } + return null; + } + private Segment GetSegment(string key) { var maybeItem = _dataStore.Get(DataModel.Segments, key); diff --git a/pkgs/sdk/server/src/Subsystems/IDataStore.cs b/pkgs/sdk/server/src/Subsystems/IDataStore.cs index 884292c4..6d2b4b96 100644 --- a/pkgs/sdk/server/src/Subsystems/IDataStore.cs +++ b/pkgs/sdk/server/src/Subsystems/IDataStore.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using static LaunchDarkly.Sdk.Server.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Server.Subsystems @@ -72,6 +74,11 @@ public interface IDataStore : IDisposable /// deleted data); null if the key is unknown ItemDescriptor? Get(DataKind kind, string key); + /// + /// Retrieves an item from the specified collection, if available. + /// + Task GetAsync(DataKind kind, string key,CancellationToken cancellation=default); + /// /// Retrieves all items from the specified collection. /// diff --git a/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs b/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs index 26bc7f28..ed8f7523 100644 --- a/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs +++ b/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs @@ -1,5 +1,6 @@ using System; - +using System.Threading; +using System.Threading.Tasks; using static LaunchDarkly.Sdk.Server.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Server.Subsystems @@ -109,6 +110,11 @@ public interface IPersistentDataStore : IDisposable /// deleted data); null if the key is unknown SerializedItemDescriptor? Get(DataKind kind, string key); + /// + /// Retrieves an item from the specified collection, if available. + /// + Task GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default); + /// /// Retrieves all items from the specified collection. /// diff --git a/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs b/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs index 4fc3b55c..2b8c876a 100644 --- a/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs +++ b/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using static LaunchDarkly.Sdk.Server.Subsystems.DataStoreTypes; @@ -14,7 +15,7 @@ namespace LaunchDarkly.Sdk.Server.Subsystems /// implementations that use a task-based asynchronous pattern. /// public interface IPersistentDataStoreAsync : IDisposable - { + { /// /// Equivalent to . /// @@ -27,9 +28,10 @@ public interface IPersistentDataStoreAsync : IDisposable /// /// specifies which collection to use /// the unique key of the item within that collection + /// optional cancellation token /// a versioned item that contains the stored data (or placeholder for /// deleted data); null if the key is unknown - Task GetAsync(DataKind kind, string key); + Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default); /// /// Equivalent to . diff --git a/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs b/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs index a1b518ec..8e60acd2 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using LaunchDarkly.Sdk.Server.Interfaces; using LaunchDarkly.Sdk.Server.Subsystems; using LaunchDarkly.TestHelpers; @@ -72,6 +74,9 @@ public void Dispose() { } public DataStoreTypes.ItemDescriptor? Get(DataStoreTypes.DataKind kind, string key) => throw new NotImplementedException(); + public Task GetAsync(DataStoreTypes.DataKind kind, string key,CancellationToken cancellationToken = default) => + throw new NotImplementedException(); + public DataStoreTypes.KeyedItems GetAll(DataStoreTypes.DataKind kind) => throw new NotImplementedException(); diff --git a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs index 67f0e1a4..796e3a60 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Sdk.Server.Subsystems; using Xunit.Abstractions; @@ -30,7 +31,7 @@ private async Task ArbitraryTask() await Task.Delay(TimeSpan.FromTicks(1)); } - public async Task GetAsync(DataKind kind, string key) + public async Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) { await ArbitraryTask(); return Get(kind, key); diff --git a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs index dbaa87aa..cc547c6a 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs @@ -11,6 +11,7 @@ using static LaunchDarkly.Sdk.Server.Subsystems.DataStoreTypes; using static LaunchDarkly.Sdk.Server.Internal.DataStores.DataStoreTestTypes; +using System.Threading.Tasks; namespace LaunchDarkly.Sdk.Server.Internal.DataStores { @@ -690,6 +691,24 @@ public void Dispose() { } return null; } + public Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + { + MaybeThrowError(); + if (Data.TryGetValue(kind, out var items)) + { + if (items.TryGetValue(key, out var item)) + { + if (PersistOnlyAsString) + { + // This simulates the kind of store implementation that can't track metadata separately + return Task.FromResult(new SerializedItemDescriptor(0, false, item.SerializedItem)); + } + return Task.FromResult(item); + } + } + return null; + } + public KeyedItems GetAll(DataKind kind) { MaybeThrowError(); From 9a259d3ef2d29052f0f1999345b1765f4aec7bc1 Mon Sep 17 00:00:00 2001 From: Jack Mott Date: Thu, 27 Feb 2025 13:48:14 -0600 Subject: [PATCH 2/7] use ValueTask --- pkgs/sdk/server/src/Interfaces/ILdClient.cs | 2 +- .../src/Internal/DataStores/InMemoryDataStore.cs | 8 ++++---- .../DataStores/PersistentStoreAsyncAdapter.cs | 6 +++--- .../Internal/DataStores/PersistentStoreWrapper.cs | 4 ++-- .../server/src/Internal/Hooks/Executor/Executor.cs | 2 +- .../src/Internal/Hooks/Executor/NoopExecutor.cs | 4 ++-- .../src/Internal/Hooks/Interfaces/IHookExecutor.cs | 4 ++-- pkgs/sdk/server/src/LdClient.cs | 12 ++++++------ pkgs/sdk/server/src/Subsystems/IDataStore.cs | 2 +- .../server/src/Subsystems/IPersistentDataStore.cs | 2 +- .../src/Subsystems/IPersistentDataStoreAsync.cs | 2 +- .../DataStores/DataStoreStatusProviderImplTest.cs | 2 +- .../DataStores/PersistentStoreWrapperTestAsync.cs | 2 +- .../DataStores/PersistentStoreWrapperTestBase.cs | 10 +++++----- 14 files changed, 31 insertions(+), 31 deletions(-) diff --git a/pkgs/sdk/server/src/Interfaces/ILdClient.cs b/pkgs/sdk/server/src/Interfaces/ILdClient.cs index 976b3145..f29f13c3 100644 --- a/pkgs/sdk/server/src/Interfaces/ILdClient.cs +++ b/pkgs/sdk/server/src/Interfaces/ILdClient.cs @@ -117,7 +117,7 @@ public interface ILdClient /// optional cancelation token /// an object /// - Task> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancelationToken = default); + ValueTask> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancelationToken = default); /// /// Calculates the integer value of a feature flag for a given context. diff --git a/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs b/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs index 7518cdf6..474e9ef2 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs @@ -62,17 +62,17 @@ public void Init(FullDataSet data) return item; } - public Task GetAsync(DataKind kind, string key, CancellationToken cancelationToken = default) + public ValueTask GetAsync(DataKind kind, string key, CancellationToken cancelationToken = default) { if (!Items.TryGetValue(kind, out var itemsOfKind)) { - return null; + return new ValueTask(); } if (!itemsOfKind.TryGetValue(key, out var item)) { - return null; + return new ValueTask(); } - return Task.FromResult(item); + return new ValueTask(Task.FromResult(item)); } public KeyedItems GetAll(DataKind kind) diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs index ee1b706d..b02aa8ad 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs @@ -31,10 +31,10 @@ public void Init(FullDataSet allData) public SerializedItemDescriptor? Get(DataKind kind, string key) { - return WaitSafely(() => _coreAsync.GetAsync(kind, key)); + return _coreAsync.GetAsync(kind, key).GetAwaiter().GetResult(); } - public Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + public ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) { return _coreAsync.GetAsync(kind, key,cancellationToken); } @@ -86,6 +86,6 @@ private T WaitSafely(Func> taskFn) .Unwrap() .GetAwaiter() .GetResult(); - } + } } } diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs index e4e2c59f..f185e9ab 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs @@ -187,7 +187,7 @@ public void Init(FullDataSet items) } } - public async Task GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default) + public async ValueTask GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default) { try { @@ -338,7 +338,7 @@ private void Dispose(bool disposing) return Deserialize(kind, maybeSerializedItem.Value); } - private async Task GetAndDeserializeItemAsync(DataKind kind, string key) + private async ValueTask GetAndDeserializeItemAsync(DataKind kind, string key) { var maybeSerializedItem = await _core.GetAsync(kind, key); if (!maybeSerializedItem.HasValue) diff --git a/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs b/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs index 82acd1e7..1cd42578 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs @@ -38,7 +38,7 @@ public Executor(Logger logger, IEnumerable hooks) return (detail, flag); } - public async Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) + public async ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) { var seriesData = _beforeEvaluation.Execute(context, default); diff --git a/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs b/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs index 3aba0419..9126c0b2 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs @@ -16,8 +16,8 @@ internal sealed class NoopExecutor: IHookExecutor public (EvaluationDetail, FeatureFlag) EvaluationSeries(EvaluationSeriesContext context, LdValue.Converter converter, Func<(EvaluationDetail, FeatureFlag)> evaluate) => evaluate(); - public Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, - LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) => evaluateAsync(); + public ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, + LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) => evaluateAsync(); public void Dispose() { diff --git a/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs b/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs index 0ce97d09..3deedbfc 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs @@ -28,7 +28,7 @@ internal interface IHookExecutor: IDisposable (EvaluationDetail, FeatureFlag) EvaluationSeries(EvaluationSeriesContext context, LdValue.Converter converter, Func<(EvaluationDetail, FeatureFlag)> evaluate); - Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, - Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default); + ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, + Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default); } } diff --git a/pkgs/sdk/server/src/LdClient.cs b/pkgs/sdk/server/src/LdClient.cs index 8803a6da..30db109d 100644 --- a/pkgs/sdk/server/src/LdClient.cs +++ b/pkgs/sdk/server/src/LdClient.cs @@ -299,8 +299,8 @@ public EvaluationDetail BoolVariationDetail(string key, Context context, b Evaluate(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons); /// - public Task> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancellationToken = default) => - EvaluateAsync(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons); + public ValueTask> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancellationToken = default) => + EvaluateAsync(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons, cancellationToken); /// public EvaluationDetail IntVariationDetail(string key, Context context, int defaultValue) => @@ -441,7 +441,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ () => EvaluationAndFlag(key, context, defaultValue, converter, checkType, eventFactory)); } - private async Task<(EvaluationDetail, FeatureFlag)> EvaluateWithHooksAsync(string method, string key, Context context, LdValue defaultValue, LdValue.Converter converter, + private async ValueTask<(EvaluationDetail, FeatureFlag)> EvaluateWithHooksAsync(string method, string key, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) { var evalSeriesContext = new EvaluationSeriesContext(key, context, defaultValue, method); @@ -549,7 +549,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ } } - private async Task<(EvaluationDetail, FeatureFlag)> EvaluationAndFlagAsync(string featureKey, Context context, + private async ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationAndFlagAsync(string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) { @@ -654,7 +654,7 @@ private EvaluationDetail Evaluate(string method, string featureKey, Contex return EvaluateWithHooks(method, featureKey, context, defaultValue, converter, checkType, eventFactory).Item1; } - private async Task> EvaluateAsync(string method, string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, + private async ValueTask> EvaluateAsync(string method, string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) { return (await EvaluateWithHooksAsync(method, featureKey, context, defaultValue, converter, checkType, eventFactory,cancellationToken)).Item1; @@ -779,7 +779,7 @@ private FeatureFlag GetFlag(string key) return null; } - private async Task GetFlagAsync(string key, CancellationToken cancellationToken = default) + private async ValueTask GetFlagAsync(string key, CancellationToken cancellationToken = default) { var maybeItem = await _dataStore.GetAsync(DataModel.Features, key,cancellationToken); if (maybeItem.HasValue && maybeItem.Value.Item != null && maybeItem.Value.Item is FeatureFlag f) diff --git a/pkgs/sdk/server/src/Subsystems/IDataStore.cs b/pkgs/sdk/server/src/Subsystems/IDataStore.cs index 6d2b4b96..65d91a1b 100644 --- a/pkgs/sdk/server/src/Subsystems/IDataStore.cs +++ b/pkgs/sdk/server/src/Subsystems/IDataStore.cs @@ -77,7 +77,7 @@ public interface IDataStore : IDisposable /// /// Retrieves an item from the specified collection, if available. /// - Task GetAsync(DataKind kind, string key,CancellationToken cancellation=default); + ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellation=default); /// /// Retrieves all items from the specified collection. diff --git a/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs b/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs index ed8f7523..81308a77 100644 --- a/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs +++ b/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs @@ -113,7 +113,7 @@ public interface IPersistentDataStore : IDisposable /// /// Retrieves an item from the specified collection, if available. /// - Task GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default); + ValueTask GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default); /// /// Retrieves all items from the specified collection. diff --git a/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs b/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs index 2b8c876a..ec40919f 100644 --- a/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs +++ b/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs @@ -31,7 +31,7 @@ public interface IPersistentDataStoreAsync : IDisposable /// optional cancellation token /// a versioned item that contains the stored data (or placeholder for /// deleted data); null if the key is unknown - Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default); + ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default); /// /// Equivalent to . diff --git a/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs b/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs index 8e60acd2..47017735 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs @@ -74,7 +74,7 @@ public void Dispose() { } public DataStoreTypes.ItemDescriptor? Get(DataStoreTypes.DataKind kind, string key) => throw new NotImplementedException(); - public Task GetAsync(DataStoreTypes.DataKind kind, string key,CancellationToken cancellationToken = default) => + public ValueTask GetAsync(DataStoreTypes.DataKind kind, string key,CancellationToken cancellationToken = default) => throw new NotImplementedException(); public DataStoreTypes.KeyedItems GetAll(DataStoreTypes.DataKind kind) => diff --git a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs index 796e3a60..cdb5e866 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs @@ -31,7 +31,7 @@ private async Task ArbitraryTask() await Task.Delay(TimeSpan.FromTicks(1)); } - public async Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + public async ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) { await ArbitraryTask(); return Get(kind, key); diff --git a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs index cc547c6a..72a1182d 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs @@ -691,8 +691,8 @@ public void Dispose() { } return null; } - public Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) - { + public ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + { MaybeThrowError(); if (Data.TryGetValue(kind, out var items)) { @@ -701,12 +701,12 @@ public void Dispose() { } if (PersistOnlyAsString) { // This simulates the kind of store implementation that can't track metadata separately - return Task.FromResult(new SerializedItemDescriptor(0, false, item.SerializedItem)); + return new ValueTask(Task.FromResult(new SerializedItemDescriptor(0, false, item.SerializedItem))); } - return Task.FromResult(item); + return new ValueTask(Task.FromResult(item)); } } - return null; + return new ValueTask(); } public KeyedItems GetAll(DataKind kind) From 749ced8869c54d2b356e29436dffd902dee1d2b3 Mon Sep 17 00:00:00 2001 From: Jack Mott Date: Thu, 27 Feb 2025 14:03:29 -0600 Subject: [PATCH 3/7] Revert "use ValueTask" This reverts commit 9a259d3ef2d29052f0f1999345b1765f4aec7bc1. --- pkgs/sdk/server/src/Interfaces/ILdClient.cs | 2 +- .../src/Internal/DataStores/InMemoryDataStore.cs | 8 ++++---- .../DataStores/PersistentStoreAsyncAdapter.cs | 6 +++--- .../Internal/DataStores/PersistentStoreWrapper.cs | 4 ++-- .../server/src/Internal/Hooks/Executor/Executor.cs | 2 +- .../src/Internal/Hooks/Executor/NoopExecutor.cs | 4 ++-- .../src/Internal/Hooks/Interfaces/IHookExecutor.cs | 4 ++-- pkgs/sdk/server/src/LdClient.cs | 12 ++++++------ pkgs/sdk/server/src/Subsystems/IDataStore.cs | 2 +- .../server/src/Subsystems/IPersistentDataStore.cs | 2 +- .../src/Subsystems/IPersistentDataStoreAsync.cs | 2 +- .../DataStores/DataStoreStatusProviderImplTest.cs | 2 +- .../DataStores/PersistentStoreWrapperTestAsync.cs | 2 +- .../DataStores/PersistentStoreWrapperTestBase.cs | 10 +++++----- 14 files changed, 31 insertions(+), 31 deletions(-) diff --git a/pkgs/sdk/server/src/Interfaces/ILdClient.cs b/pkgs/sdk/server/src/Interfaces/ILdClient.cs index f29f13c3..976b3145 100644 --- a/pkgs/sdk/server/src/Interfaces/ILdClient.cs +++ b/pkgs/sdk/server/src/Interfaces/ILdClient.cs @@ -117,7 +117,7 @@ public interface ILdClient /// optional cancelation token /// an object /// - ValueTask> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancelationToken = default); + Task> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancelationToken = default); /// /// Calculates the integer value of a feature flag for a given context. diff --git a/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs b/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs index 474e9ef2..7518cdf6 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs @@ -62,17 +62,17 @@ public void Init(FullDataSet data) return item; } - public ValueTask GetAsync(DataKind kind, string key, CancellationToken cancelationToken = default) + public Task GetAsync(DataKind kind, string key, CancellationToken cancelationToken = default) { if (!Items.TryGetValue(kind, out var itemsOfKind)) { - return new ValueTask(); + return null; } if (!itemsOfKind.TryGetValue(key, out var item)) { - return new ValueTask(); + return null; } - return new ValueTask(Task.FromResult(item)); + return Task.FromResult(item); } public KeyedItems GetAll(DataKind kind) diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs index b02aa8ad..ee1b706d 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs @@ -31,10 +31,10 @@ public void Init(FullDataSet allData) public SerializedItemDescriptor? Get(DataKind kind, string key) { - return _coreAsync.GetAsync(kind, key).GetAwaiter().GetResult(); + return WaitSafely(() => _coreAsync.GetAsync(kind, key)); } - public ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + public Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) { return _coreAsync.GetAsync(kind, key,cancellationToken); } @@ -86,6 +86,6 @@ private T WaitSafely(Func> taskFn) .Unwrap() .GetAwaiter() .GetResult(); - } + } } } diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs index f185e9ab..e4e2c59f 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs @@ -187,7 +187,7 @@ public void Init(FullDataSet items) } } - public async ValueTask GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default) + public async Task GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default) { try { @@ -338,7 +338,7 @@ private void Dispose(bool disposing) return Deserialize(kind, maybeSerializedItem.Value); } - private async ValueTask GetAndDeserializeItemAsync(DataKind kind, string key) + private async Task GetAndDeserializeItemAsync(DataKind kind, string key) { var maybeSerializedItem = await _core.GetAsync(kind, key); if (!maybeSerializedItem.HasValue) diff --git a/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs b/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs index 1cd42578..82acd1e7 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs @@ -38,7 +38,7 @@ public Executor(Logger logger, IEnumerable hooks) return (detail, flag); } - public async ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) + public async Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) { var seriesData = _beforeEvaluation.Execute(context, default); diff --git a/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs b/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs index 9126c0b2..3aba0419 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs @@ -16,8 +16,8 @@ internal sealed class NoopExecutor: IHookExecutor public (EvaluationDetail, FeatureFlag) EvaluationSeries(EvaluationSeriesContext context, LdValue.Converter converter, Func<(EvaluationDetail, FeatureFlag)> evaluate) => evaluate(); - public ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, - LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) => evaluateAsync(); + public Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, + LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) => evaluateAsync(); public void Dispose() { diff --git a/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs b/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs index 3deedbfc..0ce97d09 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs @@ -28,7 +28,7 @@ internal interface IHookExecutor: IDisposable (EvaluationDetail, FeatureFlag) EvaluationSeries(EvaluationSeriesContext context, LdValue.Converter converter, Func<(EvaluationDetail, FeatureFlag)> evaluate); - ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, - Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default); + Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, + Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default); } } diff --git a/pkgs/sdk/server/src/LdClient.cs b/pkgs/sdk/server/src/LdClient.cs index 30db109d..8803a6da 100644 --- a/pkgs/sdk/server/src/LdClient.cs +++ b/pkgs/sdk/server/src/LdClient.cs @@ -299,8 +299,8 @@ public EvaluationDetail BoolVariationDetail(string key, Context context, b Evaluate(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons); /// - public ValueTask> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancellationToken = default) => - EvaluateAsync(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons, cancellationToken); + public Task> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancellationToken = default) => + EvaluateAsync(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons); /// public EvaluationDetail IntVariationDetail(string key, Context context, int defaultValue) => @@ -441,7 +441,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ () => EvaluationAndFlag(key, context, defaultValue, converter, checkType, eventFactory)); } - private async ValueTask<(EvaluationDetail, FeatureFlag)> EvaluateWithHooksAsync(string method, string key, Context context, LdValue defaultValue, LdValue.Converter converter, + private async Task<(EvaluationDetail, FeatureFlag)> EvaluateWithHooksAsync(string method, string key, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) { var evalSeriesContext = new EvaluationSeriesContext(key, context, defaultValue, method); @@ -549,7 +549,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ } } - private async ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationAndFlagAsync(string featureKey, Context context, + private async Task<(EvaluationDetail, FeatureFlag)> EvaluationAndFlagAsync(string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) { @@ -654,7 +654,7 @@ private EvaluationDetail Evaluate(string method, string featureKey, Contex return EvaluateWithHooks(method, featureKey, context, defaultValue, converter, checkType, eventFactory).Item1; } - private async ValueTask> EvaluateAsync(string method, string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, + private async Task> EvaluateAsync(string method, string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) { return (await EvaluateWithHooksAsync(method, featureKey, context, defaultValue, converter, checkType, eventFactory,cancellationToken)).Item1; @@ -779,7 +779,7 @@ private FeatureFlag GetFlag(string key) return null; } - private async ValueTask GetFlagAsync(string key, CancellationToken cancellationToken = default) + private async Task GetFlagAsync(string key, CancellationToken cancellationToken = default) { var maybeItem = await _dataStore.GetAsync(DataModel.Features, key,cancellationToken); if (maybeItem.HasValue && maybeItem.Value.Item != null && maybeItem.Value.Item is FeatureFlag f) diff --git a/pkgs/sdk/server/src/Subsystems/IDataStore.cs b/pkgs/sdk/server/src/Subsystems/IDataStore.cs index 65d91a1b..6d2b4b96 100644 --- a/pkgs/sdk/server/src/Subsystems/IDataStore.cs +++ b/pkgs/sdk/server/src/Subsystems/IDataStore.cs @@ -77,7 +77,7 @@ public interface IDataStore : IDisposable /// /// Retrieves an item from the specified collection, if available. /// - ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellation=default); + Task GetAsync(DataKind kind, string key,CancellationToken cancellation=default); /// /// Retrieves all items from the specified collection. diff --git a/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs b/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs index 81308a77..ed8f7523 100644 --- a/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs +++ b/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs @@ -113,7 +113,7 @@ public interface IPersistentDataStore : IDisposable /// /// Retrieves an item from the specified collection, if available. /// - ValueTask GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default); + Task GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default); /// /// Retrieves all items from the specified collection. diff --git a/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs b/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs index ec40919f..2b8c876a 100644 --- a/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs +++ b/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs @@ -31,7 +31,7 @@ public interface IPersistentDataStoreAsync : IDisposable /// optional cancellation token /// a versioned item that contains the stored data (or placeholder for /// deleted data); null if the key is unknown - ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default); + Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default); /// /// Equivalent to . diff --git a/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs b/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs index 47017735..8e60acd2 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs @@ -74,7 +74,7 @@ public void Dispose() { } public DataStoreTypes.ItemDescriptor? Get(DataStoreTypes.DataKind kind, string key) => throw new NotImplementedException(); - public ValueTask GetAsync(DataStoreTypes.DataKind kind, string key,CancellationToken cancellationToken = default) => + public Task GetAsync(DataStoreTypes.DataKind kind, string key,CancellationToken cancellationToken = default) => throw new NotImplementedException(); public DataStoreTypes.KeyedItems GetAll(DataStoreTypes.DataKind kind) => diff --git a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs index cdb5e866..796e3a60 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs @@ -31,7 +31,7 @@ private async Task ArbitraryTask() await Task.Delay(TimeSpan.FromTicks(1)); } - public async ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + public async Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) { await ArbitraryTask(); return Get(kind, key); diff --git a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs index 72a1182d..cc547c6a 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs @@ -691,8 +691,8 @@ public void Dispose() { } return null; } - public ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) - { + public Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + { MaybeThrowError(); if (Data.TryGetValue(kind, out var items)) { @@ -701,12 +701,12 @@ public void Dispose() { } if (PersistOnlyAsString) { // This simulates the kind of store implementation that can't track metadata separately - return new ValueTask(Task.FromResult(new SerializedItemDescriptor(0, false, item.SerializedItem))); + return Task.FromResult(new SerializedItemDescriptor(0, false, item.SerializedItem)); } - return new ValueTask(Task.FromResult(item)); + return Task.FromResult(item); } } - return new ValueTask(); + return null; } public KeyedItems GetAll(DataKind kind) From 335200614dcdbf54b95d66e94fd35573566b93c5 Mon Sep 17 00:00:00 2001 From: Jack Mott Date: Thu, 27 Feb 2025 14:07:30 -0600 Subject: [PATCH 4/7] missing tokens --- .../src/Internal/DataStores/PersistentStoreWrapper.cs | 6 +++--- pkgs/sdk/server/src/LdClient.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs index e4e2c59f..7f8c156f 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs @@ -191,7 +191,7 @@ public void Init(FullDataSet items) { try { - var ret = _itemCache is null ? await GetAndDeserializeItemAsync(kind, key) : + var ret = _itemCache is null ? await GetAndDeserializeItemAsync(kind, key, cancellationToken) : _itemCache.Get(new CacheKey(kind, key)); ProcessError(null); return ret; @@ -338,9 +338,9 @@ private void Dispose(bool disposing) return Deserialize(kind, maybeSerializedItem.Value); } - private async Task GetAndDeserializeItemAsync(DataKind kind, string key) + private async Task GetAndDeserializeItemAsync(DataKind kind, string key, CancellationToken cancellationToken = default) { - var maybeSerializedItem = await _core.GetAsync(kind, key); + var maybeSerializedItem = await _core.GetAsync(kind, key, cancellationToken); if (!maybeSerializedItem.HasValue) { return null; diff --git a/pkgs/sdk/server/src/LdClient.cs b/pkgs/sdk/server/src/LdClient.cs index 8803a6da..f64ea459 100644 --- a/pkgs/sdk/server/src/LdClient.cs +++ b/pkgs/sdk/server/src/LdClient.cs @@ -300,7 +300,7 @@ public EvaluationDetail BoolVariationDetail(string key, Context context, b /// public Task> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancellationToken = default) => - EvaluateAsync(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons); + EvaluateAsync(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons, cancellationToken); /// public EvaluationDetail IntVariationDetail(string key, Context context, int defaultValue) => @@ -448,7 +448,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ return await _hookExecutor.EvaluationSeriesAsync( evalSeriesContext, converter, - () => EvaluationAndFlagAsync(key, context, defaultValue, converter, checkType, eventFactory,cancellationToken)); + () => EvaluationAndFlagAsync(key, context, defaultValue, converter, checkType, eventFactory,cancellationToken),cancellationToken); } private (EvaluationDetail, FeatureFlag) EvaluationAndFlag(string featureKey, Context context, From fa0e66108f4332c5b2898b25dc7b09b4a81d7745 Mon Sep 17 00:00:00 2001 From: Jack Mott Date: Fri, 28 Feb 2025 10:33:28 -0600 Subject: [PATCH 5/7] Reapply "use ValueTask" This reverts commit 749ced8869c54d2b356e29436dffd902dee1d2b3. --- pkgs/sdk/server/src/Interfaces/ILdClient.cs | 2 +- .../src/Internal/DataStores/InMemoryDataStore.cs | 8 ++++---- .../DataStores/PersistentStoreAsyncAdapter.cs | 6 +++--- .../Internal/DataStores/PersistentStoreWrapper.cs | 6 +++++- .../server/src/Internal/Hooks/Executor/Executor.cs | 2 +- .../src/Internal/Hooks/Executor/NoopExecutor.cs | 4 ++-- .../src/Internal/Hooks/Interfaces/IHookExecutor.cs | 4 ++-- pkgs/sdk/server/src/LdClient.cs | 12 ++++++++---- pkgs/sdk/server/src/Subsystems/IDataStore.cs | 2 +- .../server/src/Subsystems/IPersistentDataStore.cs | 2 +- .../src/Subsystems/IPersistentDataStoreAsync.cs | 2 +- .../DataStores/DataStoreStatusProviderImplTest.cs | 2 +- .../DataStores/PersistentStoreWrapperTestAsync.cs | 2 +- .../DataStores/PersistentStoreWrapperTestBase.cs | 10 +++++----- 14 files changed, 36 insertions(+), 28 deletions(-) diff --git a/pkgs/sdk/server/src/Interfaces/ILdClient.cs b/pkgs/sdk/server/src/Interfaces/ILdClient.cs index 976b3145..f29f13c3 100644 --- a/pkgs/sdk/server/src/Interfaces/ILdClient.cs +++ b/pkgs/sdk/server/src/Interfaces/ILdClient.cs @@ -117,7 +117,7 @@ public interface ILdClient /// optional cancelation token /// an object /// - Task> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancelationToken = default); + ValueTask> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancelationToken = default); /// /// Calculates the integer value of a feature flag for a given context. diff --git a/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs b/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs index 7518cdf6..474e9ef2 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs @@ -62,17 +62,17 @@ public void Init(FullDataSet data) return item; } - public Task GetAsync(DataKind kind, string key, CancellationToken cancelationToken = default) + public ValueTask GetAsync(DataKind kind, string key, CancellationToken cancelationToken = default) { if (!Items.TryGetValue(kind, out var itemsOfKind)) { - return null; + return new ValueTask(); } if (!itemsOfKind.TryGetValue(key, out var item)) { - return null; + return new ValueTask(); } - return Task.FromResult(item); + return new ValueTask(Task.FromResult(item)); } public KeyedItems GetAll(DataKind kind) diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs index ee1b706d..b02aa8ad 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs @@ -31,10 +31,10 @@ public void Init(FullDataSet allData) public SerializedItemDescriptor? Get(DataKind kind, string key) { - return WaitSafely(() => _coreAsync.GetAsync(kind, key)); + return _coreAsync.GetAsync(kind, key).GetAwaiter().GetResult(); } - public Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + public ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) { return _coreAsync.GetAsync(kind, key,cancellationToken); } @@ -86,6 +86,6 @@ private T WaitSafely(Func> taskFn) .Unwrap() .GetAwaiter() .GetResult(); - } + } } } diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs index 7f8c156f..7ed41a84 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs @@ -187,7 +187,7 @@ public void Init(FullDataSet items) } } - public async Task GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default) + public async ValueTask GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default) { try { @@ -338,7 +338,11 @@ private void Dispose(bool disposing) return Deserialize(kind, maybeSerializedItem.Value); } +<<<<<<< HEAD private async Task GetAndDeserializeItemAsync(DataKind kind, string key, CancellationToken cancellationToken = default) +======= + private async ValueTask GetAndDeserializeItemAsync(DataKind kind, string key) +>>>>>>> parent of 749ced8 (Revert "use ValueTask") { var maybeSerializedItem = await _core.GetAsync(kind, key, cancellationToken); if (!maybeSerializedItem.HasValue) diff --git a/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs b/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs index 82acd1e7..1cd42578 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs @@ -38,7 +38,7 @@ public Executor(Logger logger, IEnumerable hooks) return (detail, flag); } - public async Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) + public async ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) { var seriesData = _beforeEvaluation.Execute(context, default); diff --git a/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs b/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs index 3aba0419..9126c0b2 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Executor/NoopExecutor.cs @@ -16,8 +16,8 @@ internal sealed class NoopExecutor: IHookExecutor public (EvaluationDetail, FeatureFlag) EvaluationSeries(EvaluationSeriesContext context, LdValue.Converter converter, Func<(EvaluationDetail, FeatureFlag)> evaluate) => evaluate(); - public Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, - LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) => evaluateAsync(); + public ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, + LdValue.Converter converter, Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default) => evaluateAsync(); public void Dispose() { diff --git a/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs b/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs index 0ce97d09..3deedbfc 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Interfaces/IHookExecutor.cs @@ -28,7 +28,7 @@ internal interface IHookExecutor: IDisposable (EvaluationDetail, FeatureFlag) EvaluationSeries(EvaluationSeriesContext context, LdValue.Converter converter, Func<(EvaluationDetail, FeatureFlag)> evaluate); - Task<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, - Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default); + ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationSeriesAsync(EvaluationSeriesContext context, LdValue.Converter converter, + Func, FeatureFlag)>> evaluateAsync,CancellationToken cancellationToken = default); } } diff --git a/pkgs/sdk/server/src/LdClient.cs b/pkgs/sdk/server/src/LdClient.cs index f64ea459..2a322738 100644 --- a/pkgs/sdk/server/src/LdClient.cs +++ b/pkgs/sdk/server/src/LdClient.cs @@ -299,7 +299,11 @@ public EvaluationDetail BoolVariationDetail(string key, Context context, b Evaluate(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons); /// +<<<<<<< HEAD public Task> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancellationToken = default) => +======= + public ValueTask> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancellationToken = default) => +>>>>>>> parent of 749ced8 (Revert "use ValueTask") EvaluateAsync(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons, cancellationToken); /// @@ -441,7 +445,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ () => EvaluationAndFlag(key, context, defaultValue, converter, checkType, eventFactory)); } - private async Task<(EvaluationDetail, FeatureFlag)> EvaluateWithHooksAsync(string method, string key, Context context, LdValue defaultValue, LdValue.Converter converter, + private async ValueTask<(EvaluationDetail, FeatureFlag)> EvaluateWithHooksAsync(string method, string key, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) { var evalSeriesContext = new EvaluationSeriesContext(key, context, defaultValue, method); @@ -549,7 +553,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ } } - private async Task<(EvaluationDetail, FeatureFlag)> EvaluationAndFlagAsync(string featureKey, Context context, + private async ValueTask<(EvaluationDetail, FeatureFlag)> EvaluationAndFlagAsync(string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) { @@ -654,7 +658,7 @@ private EvaluationDetail Evaluate(string method, string featureKey, Contex return EvaluateWithHooks(method, featureKey, context, defaultValue, converter, checkType, eventFactory).Item1; } - private async Task> EvaluateAsync(string method, string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, + private async ValueTask> EvaluateAsync(string method, string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) { return (await EvaluateWithHooksAsync(method, featureKey, context, defaultValue, converter, checkType, eventFactory,cancellationToken)).Item1; @@ -779,7 +783,7 @@ private FeatureFlag GetFlag(string key) return null; } - private async Task GetFlagAsync(string key, CancellationToken cancellationToken = default) + private async ValueTask GetFlagAsync(string key, CancellationToken cancellationToken = default) { var maybeItem = await _dataStore.GetAsync(DataModel.Features, key,cancellationToken); if (maybeItem.HasValue && maybeItem.Value.Item != null && maybeItem.Value.Item is FeatureFlag f) diff --git a/pkgs/sdk/server/src/Subsystems/IDataStore.cs b/pkgs/sdk/server/src/Subsystems/IDataStore.cs index 6d2b4b96..65d91a1b 100644 --- a/pkgs/sdk/server/src/Subsystems/IDataStore.cs +++ b/pkgs/sdk/server/src/Subsystems/IDataStore.cs @@ -77,7 +77,7 @@ public interface IDataStore : IDisposable /// /// Retrieves an item from the specified collection, if available. /// - Task GetAsync(DataKind kind, string key,CancellationToken cancellation=default); + ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellation=default); /// /// Retrieves all items from the specified collection. diff --git a/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs b/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs index ed8f7523..81308a77 100644 --- a/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs +++ b/pkgs/sdk/server/src/Subsystems/IPersistentDataStore.cs @@ -113,7 +113,7 @@ public interface IPersistentDataStore : IDisposable /// /// Retrieves an item from the specified collection, if available. /// - Task GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default); + ValueTask GetAsync(DataKind kind, string key, CancellationToken cancellationToken = default); /// /// Retrieves all items from the specified collection. diff --git a/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs b/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs index 2b8c876a..ec40919f 100644 --- a/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs +++ b/pkgs/sdk/server/src/Subsystems/IPersistentDataStoreAsync.cs @@ -31,7 +31,7 @@ public interface IPersistentDataStoreAsync : IDisposable /// optional cancellation token /// a versioned item that contains the stored data (or placeholder for /// deleted data); null if the key is unknown - Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default); + ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default); /// /// Equivalent to . diff --git a/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs b/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs index 8e60acd2..47017735 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/DataStoreStatusProviderImplTest.cs @@ -74,7 +74,7 @@ public void Dispose() { } public DataStoreTypes.ItemDescriptor? Get(DataStoreTypes.DataKind kind, string key) => throw new NotImplementedException(); - public Task GetAsync(DataStoreTypes.DataKind kind, string key,CancellationToken cancellationToken = default) => + public ValueTask GetAsync(DataStoreTypes.DataKind kind, string key,CancellationToken cancellationToken = default) => throw new NotImplementedException(); public DataStoreTypes.KeyedItems GetAll(DataStoreTypes.DataKind kind) => diff --git a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs index 796e3a60..cdb5e866 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs @@ -31,7 +31,7 @@ private async Task ArbitraryTask() await Task.Delay(TimeSpan.FromTicks(1)); } - public async Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + public async ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) { await ArbitraryTask(); return Get(kind, key); diff --git a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs index cc547c6a..72a1182d 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs @@ -691,8 +691,8 @@ public void Dispose() { } return null; } - public Task GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) - { + public ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + { MaybeThrowError(); if (Data.TryGetValue(kind, out var items)) { @@ -701,12 +701,12 @@ public void Dispose() { } if (PersistOnlyAsString) { // This simulates the kind of store implementation that can't track metadata separately - return Task.FromResult(new SerializedItemDescriptor(0, false, item.SerializedItem)); + return new ValueTask(Task.FromResult(new SerializedItemDescriptor(0, false, item.SerializedItem))); } - return Task.FromResult(item); + return new ValueTask(Task.FromResult(item)); } } - return null; + return new ValueTask(); } public KeyedItems GetAll(DataKind kind) From e17d186123cde537f7f4bf491cc5cb86aa3083d3 Mon Sep 17 00:00:00 2001 From: Jack Mott Date: Fri, 28 Feb 2025 10:49:52 -0600 Subject: [PATCH 6/7] value task, configureawait --- .../DataStores/PersistentStoreAsyncAdapter.cs | 13 +++++++++++-- .../Internal/DataStores/PersistentStoreWrapper.cs | 10 +++------- .../server/src/Internal/Hooks/Executor/Executor.cs | 2 +- pkgs/sdk/server/src/LdClient.cs | 12 ++++-------- .../DataStores/PersistentStoreWrapperTestAsync.cs | 2 +- .../DataStores/PersistentStoreWrapperTestBase.cs | 2 +- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs index b02aa8ad..ed98c567 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreAsyncAdapter.cs @@ -31,7 +31,7 @@ public void Init(FullDataSet allData) public SerializedItemDescriptor? Get(DataKind kind, string key) { - return _coreAsync.GetAsync(kind, key).GetAwaiter().GetResult(); + return WaitSafely(() => _coreAsync.GetAsync(kind, key)); } public ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) @@ -86,6 +86,15 @@ private T WaitSafely(Func> taskFn) .Unwrap() .GetAwaiter() .GetResult(); - } + } + + private T WaitSafely(Func> taskFn) + { + return _taskFactory.StartNew(taskFn) + .GetAwaiter() + .GetResult() + .GetAwaiter() + .GetResult(); + } } } diff --git a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs index 7ed41a84..fddeaed8 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/PersistentStoreWrapper.cs @@ -191,7 +191,7 @@ public void Init(FullDataSet items) { try { - var ret = _itemCache is null ? await GetAndDeserializeItemAsync(kind, key, cancellationToken) : + var ret = _itemCache is null ? await GetAndDeserializeItemAsync(kind, key, cancellationToken).ConfigureAwait(false) : _itemCache.Get(new CacheKey(kind, key)); ProcessError(null); return ret; @@ -338,13 +338,9 @@ private void Dispose(bool disposing) return Deserialize(kind, maybeSerializedItem.Value); } -<<<<<<< HEAD - private async Task GetAndDeserializeItemAsync(DataKind kind, string key, CancellationToken cancellationToken = default) -======= - private async ValueTask GetAndDeserializeItemAsync(DataKind kind, string key) ->>>>>>> parent of 749ced8 (Revert "use ValueTask") + private async ValueTask GetAndDeserializeItemAsync(DataKind kind, string key, CancellationToken cancellationToken = default) { - var maybeSerializedItem = await _core.GetAsync(kind, key, cancellationToken); + var maybeSerializedItem = await _core.GetAsync(kind, key, cancellationToken).ConfigureAwait(false); if (!maybeSerializedItem.HasValue) { return null; diff --git a/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs b/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs index 1cd42578..9cc9470f 100644 --- a/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs +++ b/pkgs/sdk/server/src/Internal/Hooks/Executor/Executor.cs @@ -42,7 +42,7 @@ public Executor(Logger logger, IEnumerable hooks) { var seriesData = _beforeEvaluation.Execute(context, default); - var (detail, flag) = await evaluateAsync(); + var (detail, flag) = await evaluateAsync().ConfigureAwait(false); _afterEvaluation.Execute(context, new EvaluationDetail(converter.FromType(detail.Value), detail.VariationIndex, detail.Reason), seriesData); return (detail, flag); diff --git a/pkgs/sdk/server/src/LdClient.cs b/pkgs/sdk/server/src/LdClient.cs index 2a322738..4dab76b1 100644 --- a/pkgs/sdk/server/src/LdClient.cs +++ b/pkgs/sdk/server/src/LdClient.cs @@ -299,11 +299,7 @@ public EvaluationDetail BoolVariationDetail(string key, Context context, b Evaluate(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons); /// -<<<<<<< HEAD - public Task> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancellationToken = default) => -======= public ValueTask> BoolVariationDetailAsync(string key, Context context, bool defaultValue, CancellationToken cancellationToken = default) => ->>>>>>> parent of 749ced8 (Revert "use ValueTask") EvaluateAsync(Method.BoolVariationDetail, key, context, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, EventFactory.DefaultWithReasons, cancellationToken); /// @@ -452,7 +448,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ return await _hookExecutor.EvaluationSeriesAsync( evalSeriesContext, converter, - () => EvaluationAndFlagAsync(key, context, defaultValue, converter, checkType, eventFactory,cancellationToken),cancellationToken); + () => EvaluationAndFlagAsync(key, context, defaultValue, converter, checkType, eventFactory,cancellationToken),cancellationToken).ConfigureAwait(false); } private (EvaluationDetail, FeatureFlag) EvaluationAndFlag(string featureKey, Context context, @@ -584,7 +580,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[ FeatureFlag featureFlag = null; try { - featureFlag = await GetFlagAsync(featureKey,cancellationToken); + featureFlag = await GetFlagAsync(featureKey, cancellationToken).ConfigureAwait(false); if (featureFlag == null) { _evalLog.Info("Unknown feature flag \"{0}\"; returning default value", @@ -661,7 +657,7 @@ private EvaluationDetail Evaluate(string method, string featureKey, Contex private async ValueTask> EvaluateAsync(string method, string featureKey, Context context, LdValue defaultValue, LdValue.Converter converter, bool checkType, EventFactory eventFactory,CancellationToken cancellationToken = default) { - return (await EvaluateWithHooksAsync(method, featureKey, context, defaultValue, converter, checkType, eventFactory,cancellationToken)).Item1; + return (await EvaluateWithHooksAsync(method, featureKey, context, defaultValue, converter, checkType, eventFactory,cancellationToken).ConfigureAwait(false)).Item1; } /// @@ -785,7 +781,7 @@ private FeatureFlag GetFlag(string key) private async ValueTask GetFlagAsync(string key, CancellationToken cancellationToken = default) { - var maybeItem = await _dataStore.GetAsync(DataModel.Features, key,cancellationToken); + var maybeItem = await _dataStore.GetAsync(DataModel.Features, key,cancellationToken).ConfigureAwait(false); if (maybeItem.HasValue && maybeItem.Value.Item != null && maybeItem.Value.Item is FeatureFlag f) { return f; diff --git a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs index cdb5e866..ed68210b 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestAsync.cs @@ -31,7 +31,7 @@ private async Task ArbitraryTask() await Task.Delay(TimeSpan.FromTicks(1)); } - public async ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) + public new async ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) { await ArbitraryTask(); return Get(kind, key); diff --git a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs index 72a1182d..f3873626 100644 --- a/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs +++ b/pkgs/sdk/server/test/Internal/DataStores/PersistentStoreWrapperTestBase.cs @@ -692,7 +692,7 @@ public void Dispose() { } } public ValueTask GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default) - { + { MaybeThrowError(); if (Data.TryGetValue(kind, out var items)) { From 2dc00b4aac85f8c36310031b0ea1283a6e56009f Mon Sep 17 00:00:00 2001 From: Jack Mott Date: Sun, 2 Mar 2025 10:22:44 -0600 Subject: [PATCH 7/7] efficently create valuetask when we can --- pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs b/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs index 474e9ef2..0f6d4a63 100644 --- a/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs +++ b/pkgs/sdk/server/src/Internal/DataStores/InMemoryDataStore.cs @@ -72,7 +72,11 @@ public void Init(FullDataSet data) { return new ValueTask(); } +#if NET31_OR_GREATER + return ValueTask.FromResult(item); +#else return new ValueTask(Task.FromResult(item)); +#endif } public KeyedItems GetAll(DataKind kind)