diff --git a/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/RFunctionTests/ControlPanelTests.cs b/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/RFunctionTests/ControlPanelTests.cs index 2beef511..8fd9a277 100644 --- a/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/RFunctionTests/ControlPanelTests.cs +++ b/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/RFunctionTests/ControlPanelTests.cs @@ -1518,20 +1518,22 @@ protected async Task ExistingTimeoutCanBeUpdatedForAction(Task s var controlPanel = await actionRegistration.ControlPanel(flowInstance.Value); controlPanel.ShouldNotBeNull(); var timeouts = controlPanel.Timeouts; - timeouts.All.Count.ShouldBe(1); - timeouts["someTimeoutId"].ShouldBe(new DateTime(2100, 1,1, 1,1,1, DateTimeKind.Utc)); + (await timeouts.All).Count.ShouldBe(1); + await timeouts["someTimeoutId"].ShouldBeAsync(new DateTime(2100, 1,1, 1,1,1, DateTimeKind.Utc)); await timeouts.Upsert("someOtherTimeoutId", new DateTime(2101, 1, 1, 1, 1, 1, DateTimeKind.Utc)); - timeouts.All.Count.ShouldBe(2); - timeouts["someOtherTimeoutId"].ShouldBe(new DateTime(2101, 1,1, 1,1,1, DateTimeKind.Utc)); + (await timeouts.All).Count.ShouldBe(2); + await timeouts["someOtherTimeoutId"].ShouldBeAsync(new DateTime(2101, 1,1, 1,1,1, DateTimeKind.Utc)); await timeouts.Remove("someTimeoutId"); - timeouts.All.Count.ShouldBe(1); + (await timeouts.All).Count.ShouldBe(1); await controlPanel.Refresh(); - timeouts.All.Count.ShouldBe(1); - timeouts["someOtherTimeoutId"].ShouldBe(new DateTime(2101, 1,1, 1,1,1, DateTimeKind.Utc)); + (await timeouts.All).Count.ShouldBe(1); + await timeouts["someOtherTimeoutId"].ShouldBeAsync(new DateTime(2101, 1,1, 1,1,1, DateTimeKind.Utc)); + + unhandledExceptionCatcher.ShouldNotHaveExceptions(); } public abstract Task ExistingTimeoutCanBeUpdatedForFunc(); @@ -1561,20 +1563,22 @@ await workflow.Messages.Timeouts.RegisterTimeout( var controlPanel = await funcRegistration.ControlPanel(flowInstance.Value); controlPanel.ShouldNotBeNull(); var timeouts = controlPanel.Timeouts; - timeouts.All.Count.ShouldBe(1); - timeouts["someTimeoutId"].ShouldBe(new DateTime(2100, 1,1, 1,1,1, DateTimeKind.Utc)); + (await timeouts.All).Count.ShouldBe(1); + await timeouts["someTimeoutId"].ShouldBeAsync(new DateTime(2100, 1,1, 1,1,1, DateTimeKind.Utc)); await timeouts.Upsert("someOtherTimeoutId", new DateTime(2101, 1, 1, 1, 1, 1, DateTimeKind.Utc)); - timeouts.All.Count.ShouldBe(2); - timeouts["someOtherTimeoutId"].ShouldBe(new DateTime(2101, 1,1, 1,1,1, DateTimeKind.Utc)); + (await timeouts.All).Count.ShouldBe(2); + await timeouts["someOtherTimeoutId"].ShouldBeAsync(new DateTime(2101, 1,1, 1,1,1, DateTimeKind.Utc)); await timeouts.Remove("someTimeoutId"); - timeouts.All.Count.ShouldBe(1); + (await timeouts.All).Count.ShouldBe(1); await controlPanel.Refresh(); - timeouts.All.Count.ShouldBe(1); - timeouts["someOtherTimeoutId"].ShouldBe(new DateTime(2101, 1,1, 1,1,1, DateTimeKind.Utc)); + (await timeouts.All).Count.ShouldBe(1); + await timeouts["someOtherTimeoutId"].ShouldBeAsync(new DateTime(2101, 1,1, 1,1,1, DateTimeKind.Utc)); + + unhandledExceptionCatcher.ShouldNotHaveExceptions(); } public abstract Task CorrelationsCanBeChanged(); @@ -1609,6 +1613,8 @@ protected async Task CorrelationsCanBeChanged(Task storeTask) controlPanel.ShouldNotBeNull(); await controlPanel.Correlations.Contains("SomeCorrelation").ShouldBeFalseAsync(); await controlPanel.Correlations.Contains("SomeNewCorrelation").ShouldBeTrueAsync(); + + unhandledExceptionCatcher.ShouldNotHaveExceptions(); } public abstract Task DeleteRemovesFunctionFromAllStores(); @@ -1661,5 +1667,7 @@ await store.EffectsStore .GetEffectResults(functionId) .SelectAsync(e => e.Any()) .ShouldBeFalseAsync(); + + unhandledExceptionCatcher.ShouldNotHaveExceptions(); } } \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Invocation/InvocationHelper.cs b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Invocation/InvocationHelper.cs index 63b5ee4f..1855d5bb 100644 --- a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Invocation/InvocationHelper.cs +++ b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Invocation/InvocationHelper.cs @@ -418,13 +418,7 @@ public ExistingStates CreateExistingStates(FlowId flowId, string? defaultState) public ExistingEffects CreateExistingEffects(FlowId flowId) => new(flowId, _functionStore.EffectsStore, _settings.Serializer); public ExistingMessages CreateExistingMessages(FlowId flowId) => new(flowId, _functionStore.MessageStore, _settings.Serializer); - - public async Task GetExistingTimeouts(FlowId flowId) - => new ExistingTimeouts( - flowId, - _functionStore.TimeoutStore, - await _functionStore.TimeoutStore.GetTimeouts(flowId) - ); + public ExistingTimeouts CreateExistingTimeouts(FlowId flowId) => new(flowId, _functionStore.TimeoutStore); private string? SerializeParameter(TParam param) { diff --git a/Core/Cleipnir.ResilientFunctions/Domain/ControlPanel.cs b/Core/Cleipnir.ResilientFunctions/Domain/ControlPanel.cs index 113df664..b12fa83b 100644 --- a/Core/Cleipnir.ResilientFunctions/Domain/ControlPanel.cs +++ b/Core/Cleipnir.ResilientFunctions/Domain/ControlPanel.cs @@ -251,7 +251,7 @@ public async Task Refresh() Effects = _invocationHelper.CreateExistingEffects(FlowId); Messages = _invocationHelper.CreateExistingMessages(FlowId); States = _invocationHelper.CreateExistingStates(FlowId, sf.DefaultState); - Timeouts = await _invocationHelper.GetExistingTimeouts(FlowId); + Timeouts = _invocationHelper.CreateExistingTimeouts(FlowId); Correlations = _invocationHelper.CreateCorrelations(FlowId); _innerParamChanged = false; diff --git a/Core/Cleipnir.ResilientFunctions/Domain/ControlPanelFactory.cs b/Core/Cleipnir.ResilientFunctions/Domain/ControlPanelFactory.cs index 76f16d71..4840c0f8 100644 --- a/Core/Cleipnir.ResilientFunctions/Domain/ControlPanelFactory.cs +++ b/Core/Cleipnir.ResilientFunctions/Domain/ControlPanelFactory.cs @@ -35,7 +35,7 @@ internal ControlPanelFactory(FlowType flowType, Invoker invoker, Inv _invocationHelper.CreateExistingEffects(flowId), _invocationHelper.CreateExistingStates(flowId, functionState.DefaultState), _invocationHelper.CreateExistingMessages(flowId), - await _invocationHelper.GetExistingTimeouts(flowId), + _invocationHelper.CreateExistingTimeouts(flowId), _invocationHelper.CreateCorrelations(flowId), functionState.PreviouslyThrownException ); @@ -75,7 +75,7 @@ internal ControlPanelFactory(FlowType flowType, Invoker invoker, I _invocationHelper.CreateExistingEffects(flowId), _invocationHelper.CreateExistingStates(flowId, functionState.DefaultState), _invocationHelper.CreateExistingMessages(flowId), - await _invocationHelper.GetExistingTimeouts(flowId), + _invocationHelper.CreateExistingTimeouts(flowId), _invocationHelper.CreateCorrelations(flowId), functionState.PreviouslyThrownException ); @@ -115,7 +115,7 @@ internal ControlPanelFactory(FlowType flowType, Invoker invoker _invocationHelper.CreateExistingEffects(flowId), _invocationHelper.CreateExistingStates(flowId, f.DefaultState), _invocationHelper.CreateExistingMessages(flowId), - await _invocationHelper.GetExistingTimeouts(flowId), + _invocationHelper.CreateExistingTimeouts(flowId), _invocationHelper.CreateCorrelations(flowId), f.PreviouslyThrownException ); diff --git a/Core/Cleipnir.ResilientFunctions/Domain/ExistingTimeouts.cs b/Core/Cleipnir.ResilientFunctions/Domain/ExistingTimeouts.cs index 802d7242..b2ca44b1 100644 --- a/Core/Cleipnir.ResilientFunctions/Domain/ExistingTimeouts.cs +++ b/Core/Cleipnir.ResilientFunctions/Domain/ExistingTimeouts.cs @@ -2,48 +2,56 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Cleipnir.ResilientFunctions.Domain.Events; +using Cleipnir.ResilientFunctions.Helpers; using Cleipnir.ResilientFunctions.Storage; namespace Cleipnir.ResilientFunctions.Domain; -public class ExistingTimeouts +public class ExistingTimeouts(FlowId flowId, ITimeoutStore timeoutStore) { - private readonly FlowId _flowId; - private readonly ITimeoutStore _timeoutStore; - private readonly Dictionary _timeouts; - - public ExistingTimeouts(FlowId flowId, ITimeoutStore timeoutStore, IEnumerable storedTimeouts) + private Dictionary? _timeouts; + + private async Task> GetTimeouts() { - _flowId = flowId; - _timeoutStore = timeoutStore; - _timeouts = storedTimeouts.ToDictionary(s => s.TimeoutId, s => new DateTime(s.Expiry, DateTimeKind.Utc)); + if (_timeouts is not null) + return _timeouts; + + var storedTimeouts = await timeoutStore.GetTimeouts(flowId); + return _timeouts = storedTimeouts.ToDictionary( + s => new TimeoutId(s.TimeoutId), + s => new DateTime(s.Expiry, DateTimeKind.Utc) + ); } - - public DateTime this[string timeoutId] => _timeouts[timeoutId]; - - public IReadOnlyList All - => _timeouts - .Select(kv => new TimeoutEvent(kv.Key, kv.Value)) - .ToList(); - public async Task Remove(string timeoutId) + public Task this[TimeoutId timeoutId] => GetTimeouts().ContinueWith(t => t.Result[timeoutId]); + + public Task> All + => GetTimeouts().ContinueWith( + t => t.Result + .Select(kv => new RegisteredTimeout(kv.Key, kv.Value)) + .ToList() + .CastTo>() + ); + + public async Task Remove(TimeoutId timeoutId) { - await _timeoutStore.RemoveTimeout(_flowId, timeoutId); + var timeouts = await GetTimeouts(); - _timeouts.Remove(timeoutId); + await timeoutStore.RemoveTimeout(flowId, timeoutId.Value); + timeouts.Remove(timeoutId); } - public async Task Upsert(string timeoutId, DateTime expiresAt) + public async Task Upsert(TimeoutId timeoutId, DateTime expiresAt) { - await _timeoutStore.UpsertTimeout( - new StoredTimeout(_flowId, timeoutId, expiresAt.ToUniversalTime().Ticks), + var timeouts = await GetTimeouts(); + await timeoutStore.UpsertTimeout( + new StoredTimeout(flowId, timeoutId.Value, expiresAt.ToUniversalTime().Ticks), overwrite: true ); - _timeouts[timeoutId] = expiresAt; + timeouts[timeoutId] = expiresAt; } - public Task Upsert(string timeoutId, TimeSpan expiresIn) + public Task Upsert(TimeoutId timeoutId, TimeSpan expiresIn) => Upsert(timeoutId, expiresAt: DateTime.UtcNow.Add(expiresIn)); } \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions/Domain/TimeoutId.cs b/Core/Cleipnir.ResilientFunctions/Domain/TimeoutId.cs new file mode 100644 index 00000000..d4d9ac6b --- /dev/null +++ b/Core/Cleipnir.ResilientFunctions/Domain/TimeoutId.cs @@ -0,0 +1,24 @@ +using System; + +namespace Cleipnir.ResilientFunctions.Domain; + +public class TimeoutId +{ + public string Value { get; } + public TimeoutId(string value) + { + ArgumentNullException.ThrowIfNull(value); + Delimiters.EnsureNoUnitSeparator(value); + + Value = value; + } + + public static implicit operator TimeoutId(string flowInstance) => new(flowInstance); + public override string ToString() => Value; + public static bool operator ==(TimeoutId id1, TimeoutId id2) => id1.Equals(id2); + public static bool operator !=(TimeoutId id1, TimeoutId id2) => !(id1 == id2); + + public override bool Equals(object? obj) + => obj is TimeoutId id && id.Value == Value; + public override int GetHashCode() => Value.GetHashCode(); +} \ No newline at end of file