From 9ecc71ba0c8d2a7ae5eb6b5867c0f8e27033288e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Milewski?= Date: Tue, 27 Aug 2024 10:53:17 +0200 Subject: [PATCH] Several fixes - Cancelling of engine's main loop - Update() method on context values - Scheduling of startup actions moved to IHostApplicationLifetime - Activity's exception is now breaking execution - dev experience! --- .../Stateflows.Common.Transport.csproj | 4 ++ .../ActivitiesDependencyInjection.cs | 6 ++ Core/Stateflows/Activities/Engine/Executor.cs | 38 ++++++++-- .../Stateflows/Activities/Engine/Inspector.cs | 63 ++++++++++------- .../Interfaces/IActivityExceptionHandler.cs | 28 ++++---- Core/Stateflows/Activities/Models/Node.cs | 6 +- .../Registration/Builders/ActivityBuilder.cs | 18 ++++- .../Builders/BaseActivityBuilder.cs | 45 +++++++++++- .../Registration/Builders/FlowBuilder.cs | 35 +++++++--- .../Common/Classes/BaseValueAccessor.cs | 3 + .../Common/Classes/ContextValuesCollection.cs | 53 ++++++++++---- Core/Stateflows/Common/Classes/EventQueue.cs | 29 ++++++++ .../Common/Engine/StateflowsEngine.cs | 5 +- .../Common/Initializer/ThreadInitializer.cs | 69 ------------------- .../Common/Interfaces/IContextValues.cs | 4 +- .../Common/Scheduler/ScheduleExecutor.cs | 6 +- .../{ThreadScheduler.cs => Scheduler.cs} | 48 ++++++++++--- .../Common/Scheduler/StartupExecutor.cs | 6 +- Core/Stateflows/DependencyInjection.cs | 3 +- .../StateMachines/Engine/Executor.cs | 1 - .../Blazor/Server/Blazor.Server/Program.cs | 61 +++++++++++----- .../Stateflows.Locks.DistributedLock.csproj | 4 ++ ...teflows.Storage.EntityFrameworkCore.csproj | 4 ++ .../Stateflows.Transport.Http.csproj | 5 ++ 24 files changed, 369 insertions(+), 175 deletions(-) delete mode 100644 Core/Stateflows/Common/Initializer/ThreadInitializer.cs rename Core/Stateflows/Common/Scheduler/{ThreadScheduler.cs => Scheduler.cs} (64%) diff --git a/Core/Stateflows.Transport.Common/Stateflows.Common.Transport.csproj b/Core/Stateflows.Transport.Common/Stateflows.Common.Transport.csproj index 3b3de483..016edf2c 100644 --- a/Core/Stateflows.Transport.Common/Stateflows.Common.Transport.csproj +++ b/Core/Stateflows.Transport.Common/Stateflows.Common.Transport.csproj @@ -35,4 +35,8 @@ + + + + diff --git a/Core/Stateflows/Activities/ActivitiesDependencyInjection.cs b/Core/Stateflows/Activities/ActivitiesDependencyInjection.cs index 3aa8424c..ae5be48b 100644 --- a/Core/Stateflows/Activities/ActivitiesDependencyInjection.cs +++ b/Core/Stateflows/Activities/ActivitiesDependencyInjection.cs @@ -11,6 +11,7 @@ using Stateflows.Activities.EventHandlers; using Stateflows.Activities.Registration.Builders; using Stateflows.Activities.Registration.Interfaces; +using Stateflows.Common.Initializer; namespace Stateflows.Activities { @@ -26,6 +27,11 @@ public static IStateflowsBuilder AddActivities(this IStateflowsBuilder stateflow return stateflowsBuilder; } + [DebuggerHidden] + public static IStateflowsBuilder AddDefaultInstance(this IStateflowsBuilder stateflowsBuilder, DefaultInstanceInitializationRequestFactoryAsync initializationRequestFactoryAsync = null) + where TActivity : class, IActivity + => stateflowsBuilder.AddDefaultInstance(new StateMachineClass(Activity.Name).BehaviorClass, initializationRequestFactoryAsync); + private static ActivitiesRegister EnsureActivitiesServices(this IStateflowsBuilder stateflowsBuilder) { if (!Registers.TryGetValue(stateflowsBuilder, out var register)) diff --git a/Core/Stateflows/Activities/Engine/Executor.cs b/Core/Stateflows/Activities/Engine/Executor.cs index e8d5ad2c..44ec0635 100644 --- a/Core/Stateflows/Activities/Engine/Executor.cs +++ b/Core/Stateflows/Activities/Engine/Executor.cs @@ -13,6 +13,7 @@ using Stateflows.Activities.Streams; using Stateflows.Activities.Registration; using Stateflows.Activities.Context.Classes; +using Stateflows.Common.Exceptions; namespace Stateflows.Activities.Engine { @@ -335,6 +336,21 @@ public async Task DoFinalizeAsync(IEnumerable outputTokens = null) await Inspector.BeforeActivityFinalizationAsync(context); await Graph.Finalize.WhenAll(context); + try + { + await Graph.Finalize.WhenAll(context); + } + catch (Exception e) + { + if (!await Inspector.OnActivityFinalizationExceptionAsync(context, e)) + { + throw; + } + else + { + throw new ExecutionException(e); + } + } await Inspector.AfterActivityFinalizationAsync(context); @@ -541,7 +557,21 @@ public async Task DoInitializeActivityAsync(ActivityInitia } catch (Exception e) { - await Inspector.OnActivityInitializationExceptionAsync(context, context.InitializationEvent, e); + if (e is StateflowsException) + { + throw; + } + else + { + if (!await Inspector.OnActivityInitializationExceptionAsync(context, context.InitializationEvent, e)) + { + throw; + } + else + { + throw new ExecutionException(e); + } + } result = InitializationStatus.NotInitialized; } @@ -563,7 +593,7 @@ public async Task DoInitializeActivityAsync(ActivityInitia return result; } - public async Task> HandleExceptionAsync(Node node, Exception exception, BaseContext context) + public async Task HandleExceptionAsync(Node node, Exception exception, BaseContext context) { Node handler = null; var currentNode = node; @@ -615,10 +645,10 @@ public async Task> HandleExceptionAsync(Node node, Exce ReportExceptionHandled(node, exceptionName, exceptionContext.OutputTokens.Where(t => t is TokenHolder).ToArray(), Context); - return exceptionContext.OutputTokens; + return true; } - return new TokenHolder[0]; + return false; } public async Task DoHandleNodeAsync(Node node, Edge upstreamEdge, NodeScope nodeScope, IEnumerable input = null, IEnumerable selectionTokens = null) diff --git a/Core/Stateflows/Activities/Engine/Inspector.cs b/Core/Stateflows/Activities/Engine/Inspector.cs index 25ee6294..919d51d6 100644 --- a/Core/Stateflows/Activities/Engine/Inspector.cs +++ b/Core/Stateflows/Activities/Engine/Inspector.cs @@ -195,82 +195,99 @@ public async Task AfterNodeActivateAsync(ActionContext context) await Plugins.RunSafe(p => p.AfterNodeActivateAsync(context), nameof(AfterNodeActivateAsync), Logger); } - public async Task OnActivityInitializationExceptionAsync(BaseContext context, Event initializationEvent, Exception exception) + private static bool ShouldPropagateException(Graph graph, bool handled) + => !handled; + + public async Task OnActivityInitializationExceptionAsync(BaseContext context, Event initializationEvent, Exception exception) { var exceptionContext = new ActivityInitializationContext(context, initializationEvent, null); - await ExceptionHandlers.RunSafe(h => h.OnActivityInitializationExceptionAsync(exceptionContext, exception), nameof(OnActivityInitializationExceptionAsync), Logger); + var handled = await ExceptionHandlers.RunSafe(h => h.OnActivityInitializationExceptionAsync(exceptionContext, exception), nameof(OnActivityInitializationExceptionAsync), Logger, false); await Inspectors.RunSafe(i => i.OnActivityInitializationExceptionAsync(exceptionContext, exception), nameof(OnActivityInitializationExceptionAsync), Logger); - if (!ExceptionHandlers.Any()) + if (ShouldPropagateException(context.Context.Executor.Graph, handled)) { context.Context.Exceptions.Add(exception); } + + return handled; } - public async Task OnActivityFinalizationExceptionAsync(ActivityActionContext context, Exception exception) + public async Task OnActivityFinalizationExceptionAsync(ActivityActionContext context, Exception exception) { - await ExceptionHandlers.RunSafe(h => h.OnActivityFinalizationExceptionAsync(context, exception), nameof(OnActivityFinalizationExceptionAsync), Logger); + var handled = await ExceptionHandlers.RunSafe(h => h.OnActivityFinalizationExceptionAsync(context, exception), nameof(OnActivityFinalizationExceptionAsync), Logger, false); await Inspectors.RunSafe(i => i.OnActivityFinalizationExceptionAsync(context, exception), nameof(OnActivityFinalizationExceptionAsync), Logger); - if (!ExceptionHandlers.Any()) + if (ShouldPropagateException(context.Context.Executor.Graph, handled)) { context.Context.Exceptions.Add(exception); } + + return handled; } - public async Task OnNodeInitializationExceptionAsync(ActivityNodeContext context, Exception exception) + public async Task OnNodeInitializationExceptionAsync(ActivityNodeContext context, Exception exception) { - await ExceptionHandlers.RunSafe(h => h.OnNodeInitializationExceptionAsync(context, exception), nameof(OnNodeInitializationExceptionAsync), Logger); + var handled = await ExceptionHandlers.RunSafe(h => h.OnNodeInitializationExceptionAsync(context, exception), nameof(OnNodeInitializationExceptionAsync), Logger, false); await Inspectors.RunSafe(i => i.OnNodeInitializationExceptionAsync(context, exception), nameof(OnNodeInitializationExceptionAsync), Logger); - if (!ExceptionHandlers.Any()) + if (ShouldPropagateException(context.Context.Executor.Graph, handled)) { context.Context.Exceptions.Add(exception); } + + return handled; } - public async Task OnNodeFinalizationExceptionAsync(ActivityNodeContext context, Exception exception) + public async Task OnNodeFinalizationExceptionAsync(ActivityNodeContext context, Exception exception) { - await ExceptionHandlers.RunSafe(h => h.OnNodeFinalizationExceptionAsync(context, exception), nameof(OnNodeFinalizationExceptionAsync), Logger); + var handled = await ExceptionHandlers.RunSafe(h => h.OnNodeFinalizationExceptionAsync(context, exception), nameof(OnNodeFinalizationExceptionAsync), Logger, false); await Inspectors.RunSafe(i => i.OnNodeFinalizationExceptionAsync(context, exception), nameof(OnNodeFinalizationExceptionAsync), Logger); - if (!ExceptionHandlers.Any()) + if (ShouldPropagateException(context.Context.Executor.Graph, handled)) { context.Context.Exceptions.Add(exception); } + + return handled; } - public async Task OnNodeExecutionExceptionAsync(ActivityNodeContext context, Exception exception) + public async Task OnNodeExecutionExceptionAsync(ActivityNodeContext context, Exception exception) { - await ExceptionHandlers.RunSafe(h => h.OnNodeExecutionExceptionAsync(context, exception), nameof(OnNodeExecutionExceptionAsync), Logger); + var handled = await ExceptionHandlers.RunSafe(h => h.OnNodeExecutionExceptionAsync(context, exception), nameof(OnNodeExecutionExceptionAsync), Logger, false); await Inspectors.RunSafe(i => i.OnNodeExecutionExceptionAsync(context, exception), nameof(OnNodeExecutionExceptionAsync), Logger); - if (!ExceptionHandlers.Any()) + if (ShouldPropagateException(context.Context.Executor.Graph, handled)) { context.Context.Exceptions.Add(exception); } + + return handled; } - public async Task OnFlowGuardExceptionAsync(IGuardInspectionContext context, Exception exception) + public async Task OnFlowGuardExceptionAsync(IGuardInspectionContext context, Exception exception) { - await ExceptionHandlers.RunSafe(h => h.OnFlowGuardExceptionAsync(context, exception), nameof(OnFlowGuardExceptionAsync), Logger); + var handled = await ExceptionHandlers.RunSafe(h => h.OnFlowGuardExceptionAsync(context, exception), nameof(OnFlowGuardExceptionAsync), Logger, false); await Inspectors.RunSafe(i => i.OnFlowGuardExceptionAsync(context, exception), nameof(OnFlowGuardExceptionAsync), Logger); - //if (!ExceptionHandlers.Any()) + //if (ShouldPropagateException(context.Context.Executor.Graph, handled)) //{ - // context.Exceptions.Add(exception); + // context.Context.Exceptions.Add(exception); //} + + return handled; } - public async Task OnFlowTransformationExceptionAsync(ITransformationInspectionContext context, Exception exception) + public async Task OnFlowTransformationExceptionAsync(ITransformationInspectionContext context, Exception exception) { - await ExceptionHandlers.RunSafe(h => h.OnFlowTransformationExceptionAsync(context, exception), nameof(OnFlowTransformationExceptionAsync), Logger); + var handled = await ExceptionHandlers.RunSafe(h => h.OnFlowTransformationExceptionAsync(context, exception), nameof(OnFlowTransformationExceptionAsync), Logger, false); await Inspectors.RunSafe(i => i.OnFlowTransformationExceptionAsync(context, exception), nameof(OnFlowTransformationExceptionAsync), Logger); - //if (!ExceptionHandlers.Any()) + //if (ShouldPropagateException(context.Context.Executor.Graph, handled)) //{ - // context.Exceptions.Add(exception); + // context.Context.Exceptions.Add(exception); //} + + return handled; } } } diff --git a/Core/Stateflows/Activities/Interfaces/IActivityExceptionHandler.cs b/Core/Stateflows/Activities/Interfaces/IActivityExceptionHandler.cs index 714ba8f2..4f03fc55 100644 --- a/Core/Stateflows/Activities/Interfaces/IActivityExceptionHandler.cs +++ b/Core/Stateflows/Activities/Interfaces/IActivityExceptionHandler.cs @@ -6,26 +6,26 @@ namespace Stateflows.Activities { public interface IActivityExceptionHandler { - Task OnActivityInitializationExceptionAsync(IActivityInitializationContext context, Exception exception) - => Task.CompletedTask; + Task OnActivityInitializationExceptionAsync(IActivityInitializationContext context, Exception exception) + => Task.FromResult(false); - Task OnActivityFinalizationExceptionAsync(IActivityFinalizationContext context, Exception exception) - => Task.CompletedTask; + Task OnActivityFinalizationExceptionAsync(IActivityFinalizationContext context, Exception exception) + => Task.FromResult(false); - Task OnNodeInitializationExceptionAsync(IActivityNodeContext context, Exception exception) - => Task.CompletedTask; + Task OnNodeInitializationExceptionAsync(IActivityNodeContext context, Exception exception) + => Task.FromResult(false); - Task OnNodeFinalizationExceptionAsync(IActivityNodeContext context, Exception exception) - => Task.CompletedTask; + Task OnNodeFinalizationExceptionAsync(IActivityNodeContext context, Exception exception) + => Task.FromResult(false); - Task OnNodeExecutionExceptionAsync(IActivityNodeContext context, Exception exception) - => Task.CompletedTask; + Task OnNodeExecutionExceptionAsync(IActivityNodeContext context, Exception exception) + => Task.FromResult(false); - Task OnFlowGuardExceptionAsync(IGuardContext context, Exception exception) - => Task.CompletedTask; + Task OnFlowGuardExceptionAsync(IGuardContext context, Exception exception) + => Task.FromResult(false); - Task OnFlowTransformationExceptionAsync(ITransformationContext context, Exception exception) - => Task.CompletedTask; + Task OnFlowTransformationExceptionAsync(ITransformationContext context, Exception exception) + => Task.FromResult(false); } } diff --git a/Core/Stateflows/Activities/Models/Node.cs b/Core/Stateflows/Activities/Models/Node.cs index db1dad1a..3a1fd75f 100644 --- a/Core/Stateflows/Activities/Models/Node.cs +++ b/Core/Stateflows/Activities/Models/Node.cs @@ -163,7 +163,7 @@ public IEnumerable ExceptionHandlers .Select(e => e.Target) .Where(n => n.Type == NodeType.ExceptionHandler); - public async Task> HandleExceptionAsync(Exception exception, BaseContext context) + public async Task HandleExceptionAsync(Exception exception, BaseContext context) { Node handler = null; var currentNode = this; @@ -205,10 +205,10 @@ public async Task> HandleExceptionAsync(Exception exception, await handler.Action.WhenAll(exceptionContext); - return exceptionContext.OutputTokens; + return true; } - return new TokenHolder[0]; + return false; } } } diff --git a/Core/Stateflows/Activities/Registration/Builders/ActivityBuilder.cs b/Core/Stateflows/Activities/Registration/Builders/ActivityBuilder.cs index cec79b71..65d590e4 100644 --- a/Core/Stateflows/Activities/Registration/Builders/ActivityBuilder.cs +++ b/Core/Stateflows/Activities/Registration/Builders/ActivityBuilder.cs @@ -10,6 +10,7 @@ using Stateflows.Activities.Registration.Extensions; using Stateflows.Activities.Registration.Interfaces; using Stateflows.Activities.Registration.Interfaces.Base; +using Stateflows.Common.Exceptions; namespace Stateflows.Activities.Registration.Builders { @@ -91,7 +92,22 @@ public IActivityBuilder AddInitializer(Func action } catch (Exception e) { - await context.Context.Executor.Inspector.OnNodeFinalizationExceptionAsync(context, e); + if (e is StateflowsException) + { + throw; + } + else + { + if (!await context.Context.Executor.Inspector.OnNodeFinalizationExceptionAsync(context, e)) + { + throw; + } + else + { + throw new ExecutionException(e); + } + } } }); @@ -337,7 +362,21 @@ public BaseActivityBuilder AddOnInitialize(Func acti } catch (Exception e) { - await context.Context.Executor.Inspector.OnNodeInitializationExceptionAsync(context, e); + if (e is StateflowsException) + { + throw; + } + else + { + if (!await context.Context.Executor.Inspector.OnNodeInitializationExceptionAsync(context, e)) + { + throw; + } + else + { + throw new ExecutionException(e); + } + } } }); diff --git a/Core/Stateflows/Activities/Registration/Builders/FlowBuilder.cs b/Core/Stateflows/Activities/Registration/Builders/FlowBuilder.cs index 472bc5c6..418dda7d 100644 --- a/Core/Stateflows/Activities/Registration/Builders/FlowBuilder.cs +++ b/Core/Stateflows/Activities/Registration/Builders/FlowBuilder.cs @@ -5,6 +5,7 @@ using Stateflows.Activities.Context.Interfaces; using Stateflows.Activities.Registration.Interfaces; using Stateflows.Utils; +using Stateflows.Common.Exceptions; namespace Stateflows.Activities.Registration.Builders { @@ -43,12 +44,21 @@ await guardAsync(new TokenFlowContext(context, token.Payload)) } catch (Exception e) { - if (Edge.Source != null) + if (e is StateflowsException) { - await Edge.Source.HandleExceptionAsync(e, context); + throw; + } + else + { + if (!(Edge.Source != null && await Edge.Source.HandleExceptionAsync(e, context))) + { + throw; + } + else + { + throw new ExecutionException(e); + } } - - return null; } }); @@ -75,12 +85,21 @@ context.Token is TokenHolder token } catch (Exception e) { - if (Edge.Source != null) + if (e is StateflowsException) { - await Edge.Source.HandleExceptionAsync(e, context); + throw; + } + else + { + if (!(Edge.Source != null && await Edge.Source.HandleExceptionAsync(e, context))) + { + throw; + } + else + { + throw new ExecutionException(e); + } } - - return null; } }); diff --git a/Core/Stateflows/Common/Classes/BaseValueAccessor.cs b/Core/Stateflows/Common/Classes/BaseValueAccessor.cs index 554eea41..2c821785 100644 --- a/Core/Stateflows/Common/Classes/BaseValueAccessor.cs +++ b/Core/Stateflows/Common/Classes/BaseValueAccessor.cs @@ -27,6 +27,9 @@ public bool TryGet(out T value) public T GetOrDefault(T defaultValue = default) => valueSet.GetOrDefault(valueName, defaultValue); + public void Update(Func valueUpdater, T defaultValue = default) + => valueSet.Update(valueName, valueUpdater, defaultValue); + public void Remove() => valueSet.Remove(valueName); } diff --git a/Core/Stateflows/Common/Classes/ContextValuesCollection.cs b/Core/Stateflows/Common/Classes/ContextValuesCollection.cs index a6dadc6a..989ae35d 100644 --- a/Core/Stateflows/Common/Classes/ContextValuesCollection.cs +++ b/Core/Stateflows/Common/Classes/ContextValuesCollection.cs @@ -2,6 +2,7 @@ using Stateflows.Common.Utilities; using Stateflows.Common.Interfaces; using System.Collections.Generic; +using System; namespace Stateflows.Common.Classes { @@ -14,11 +15,16 @@ public ContextValuesCollection(Dictionary values) Values = values; } + private void InternalSet(string key, T value) + { + Values[key] = StateflowsJsonConverter.SerializePolymorphicObject(value); + } + public void Set(string key, T value) { lock (Values) { - Values[key] = StateflowsJsonConverter.SerializePolymorphicObject(value); + InternalSet(key, value); } } @@ -61,29 +67,46 @@ public bool TryGet(string key, out T value) return false; } - public T GetOrDefault(string key, T defaultValue) + private T InternalGetOrDefault(string key, T defaultValue) { - lock (Values) + if (Values.TryGetValue(key, out var data)) { - if (Values.TryGetValue(key, out var data)) + var type = typeof(T); + var deserializedData = type.IsPrimitive + ? ParseStringToTypedValue(data) + : type.IsEnum + ? ParseStringToEnum(data) + : StateflowsJsonConverter.DeserializeObject(data); + + if (deserializedData is T t) { - var type = typeof(T); - var deserializedData = type.IsPrimitive - ? ParseStringToTypedValue(data) - : type.IsEnum - ? ParseStringToEnum(data) - : StateflowsJsonConverter.DeserializeObject(data); - - if (deserializedData is T t) - { - return t; - } + return t; } } return defaultValue; } + public T GetOrDefault(string key, T defaultValue = default) + { + lock (Values) + { + return InternalGetOrDefault(key, defaultValue); + } + } + + public void Update(string key, Func valueUpdater, T defaultValue = default) + { + lock (Values) + { + var value = InternalGetOrDefault(key, defaultValue); + + value = valueUpdater(value); + + InternalSet(key, value); + } + } + public void Remove(string key) { lock (Values) diff --git a/Core/Stateflows/Common/Classes/EventQueue.cs b/Core/Stateflows/Common/Classes/EventQueue.cs index dd91508b..92faa3c8 100644 --- a/Core/Stateflows/Common/Classes/EventQueue.cs +++ b/Core/Stateflows/Common/Classes/EventQueue.cs @@ -26,6 +26,35 @@ protected EventQueue(SerializationInfo info, StreamingContext context) : base(in private readonly string QueueEmpty = "Queue empty."; + public async Task WaitAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + await WaitAsync(10 * 1000); + + var enqueued = false; + + if (Locked) + { + lock (LockObject) + { + enqueued = Count > 0; + } + } + else + { + enqueued = Count > 0; + } + + if (enqueued) + { + return true; + } + } + + return false; + } + public Task WaitAsync(int millisecondsTimeout = -1) => Event.WaitOneAsync(millisecondsTimeout); diff --git a/Core/Stateflows/Common/Engine/StateflowsEngine.cs b/Core/Stateflows/Common/Engine/StateflowsEngine.cs index e3ecfcda..feb841f1 100644 --- a/Core/Stateflows/Common/Engine/StateflowsEngine.cs +++ b/Core/Stateflows/Common/Engine/StateflowsEngine.cs @@ -57,7 +57,10 @@ public Task StartAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { - EventQueue.WaitAsync().GetAwaiter().GetResult(); + if (!EventQueue.WaitAsync(cancellationToken).GetAwaiter().GetResult()) + { + continue; + } var token = EventQueue.Dequeue(); diff --git a/Core/Stateflows/Common/Initializer/ThreadInitializer.cs b/Core/Stateflows/Common/Initializer/ThreadInitializer.cs deleted file mode 100644 index a99b6997..00000000 --- a/Core/Stateflows/Common/Initializer/ThreadInitializer.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.DependencyInjection; - -namespace Stateflows.Common.Initializer -{ - internal class ThreadInitializer : IHostedService - { - private readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); - - private readonly IStateflowsTenantExecutor Executor; - private readonly IBehaviorLocator Locator; - private readonly IServiceScope Scope; - private readonly ILogger Logger; - - private IServiceProvider ServiceProvider - => Scope.ServiceProvider; - - public ThreadInitializer(IServiceProvider serviceProvider) - { - Scope = serviceProvider.CreateScope(); - Executor = ServiceProvider.GetRequiredService(); - Locator = ServiceProvider.GetRequiredService(); - Logger = ServiceProvider.GetRequiredService>(); - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - try - { - await Executor.ExecuteByTenantsAsync(() => InitiateBehaviors()); - } - catch (Exception e) - { - Logger.LogError(LogTemplates.ExceptionLogTemplate, typeof(ThreadInitializer).FullName, nameof(StartAsync), e.GetType().Name, e.Message); - } - } - - private async Task InitiateBehaviors() - { - var tokens = BehaviorClassesInitializations.Instance.DefaultInstanceInitializationTokens; - - await Task.WhenAll( - tokens.Select(async token => - { - token.RefreshEnvironment(); - - if (Locator.TryLocateBehavior(new BehaviorId(token.BehaviorClass, string.Empty), out var behavior)) - { - await behavior.SendAsync(await token.InitializationRequestFactory(ServiceProvider, token.BehaviorClass)); - } - }) - ); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - CancellationTokenSource.Cancel(); - - Scope.Dispose(); - - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/Core/Stateflows/Common/Interfaces/IContextValues.cs b/Core/Stateflows/Common/Interfaces/IContextValues.cs index 2c7d837b..3c785ebf 100644 --- a/Core/Stateflows/Common/Interfaces/IContextValues.cs +++ b/Core/Stateflows/Common/Interfaces/IContextValues.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System; namespace Stateflows.Common.Interfaces { @@ -12,6 +12,8 @@ public interface IContextValues T GetOrDefault(string key, T defaultValue = default); + void Update(string key, Func valueUpdater, T defaultValue = default); + void Remove(string key); void Clear(); diff --git a/Core/Stateflows/Common/Scheduler/ScheduleExecutor.cs b/Core/Stateflows/Common/Scheduler/ScheduleExecutor.cs index 0ab71012..0642829d 100644 --- a/Core/Stateflows/Common/Scheduler/ScheduleExecutor.cs +++ b/Core/Stateflows/Common/Scheduler/ScheduleExecutor.cs @@ -15,7 +15,7 @@ internal class ScheduleExecutor private readonly IBehaviorLocator Locator; - private readonly ILogger Logger; + private readonly ILogger Logger; private readonly TimeSpan LockTimeout = new TimeSpan(0, 0, 10); @@ -28,7 +28,7 @@ public ScheduleExecutor( IStateflowsLock @lock, IBehaviorClassesProvider behaviorClassesProvider, IBehaviorLocator behaviorLocator, - ILogger logger + ILogger logger ) { Services = services; @@ -36,7 +36,7 @@ ILogger logger BehaviorClassesProvider = behaviorClassesProvider; Locator = behaviorLocator; Logger = logger; - HandlingLockId = new BehaviorId(nameof(ThreadScheduler), nameof(HandlingLockId), new BehaviorClass("", "").Environment); + HandlingLockId = new BehaviorId(nameof(Scheduler), nameof(HandlingLockId), new BehaviorClass("", "").Environment); } public async Task ExecuteAsync() diff --git a/Core/Stateflows/Common/Scheduler/ThreadScheduler.cs b/Core/Stateflows/Common/Scheduler/Scheduler.cs similarity index 64% rename from Core/Stateflows/Common/Scheduler/ThreadScheduler.cs rename to Core/Stateflows/Common/Scheduler/Scheduler.cs index e0af578b..3b555bdb 100644 --- a/Core/Stateflows/Common/Scheduler/ThreadScheduler.cs +++ b/Core/Stateflows/Common/Scheduler/Scheduler.cs @@ -4,31 +4,46 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; +using Stateflows.Common.Initializer; +using System.Linq; namespace Stateflows.Common.Scheduler { - internal class ThreadScheduler : IHostedService + internal class Scheduler : IHostedService { private readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); private readonly IStateflowsTenantExecutor Executor; private readonly IServiceScope Scope; - private readonly ILogger Logger; + private readonly ILogger Logger; + private readonly IBehaviorLocator Locator; + private readonly IHostApplicationLifetime Lifetime; private IServiceProvider ServiceProvider => Scope.ServiceProvider; - public ThreadScheduler(IServiceProvider serviceProvider) + public Scheduler(IServiceProvider serviceProvider, IHostApplicationLifetime lifetime) { Scope = serviceProvider.CreateScope(); Executor = ServiceProvider.GetRequiredService(); - Logger = ServiceProvider.GetRequiredService>(); + Logger = ServiceProvider.GetRequiredService>(); + Locator = ServiceProvider.GetRequiredService(); + Lifetime = lifetime; + } + + public void ApplicationStarted() + { + Task.WaitAll( + Executor.ExecuteByTenantsAsync(() => InitiateBehaviors()), + Executor.ExecuteByTenantsAsync(() => HandleStartupEvents()) + ); } public Task StartAsync(CancellationToken cancellationToken) { + Lifetime.ApplicationStarted.Register(ApplicationStarted); + _ = Task.Run(async () => { - await Executor.ExecuteByTenantsAsync(() => HandleStartupEvents()); await TimingLoop(CancellationTokenSource.Token); }); @@ -56,7 +71,7 @@ private async Task TimingLoop(CancellationToken cancellationToken) } catch (Exception e) { - Logger.LogError(LogTemplates.ExceptionLogTemplate, typeof(ThreadScheduler).FullName, nameof(TimingLoop), e.GetType().Name, e.Message); + Logger.LogError(LogTemplates.ExceptionLogTemplate, typeof(Scheduler).FullName, nameof(TimingLoop), e.GetType().Name, e.Message); } } @@ -76,10 +91,27 @@ private async Task HandleTimeEvents() } catch (Exception e) { - Logger.LogError(LogTemplates.ExceptionLogTemplate, typeof(ThreadScheduler).FullName, nameof(HandleTimeEvents), e.GetType().Name, e.Message); + Logger.LogError(LogTemplates.ExceptionLogTemplate, typeof(Scheduler).FullName, nameof(HandleTimeEvents), e.GetType().Name, e.Message); } } + private async Task InitiateBehaviors() + { + var tokens = BehaviorClassesInitializations.Instance.DefaultInstanceInitializationTokens; + + await Task.WhenAll( + tokens.Select(async token => + { + token.RefreshEnvironment(); + + if (Locator.TryLocateBehavior(new BehaviorId(token.BehaviorClass, string.Empty), out var behavior)) + { + await behavior.SendAsync(await token.InitializationRequestFactory(ServiceProvider, token.BehaviorClass)); + } + }) + ); + } + private async Task HandleStartupEvents() { using var scope = ServiceProvider.CreateScope(); @@ -92,7 +124,7 @@ private async Task HandleStartupEvents() } catch (Exception e) { - Logger.LogError(LogTemplates.ExceptionLogTemplate, typeof(ThreadScheduler).FullName, nameof(HandleStartupEvents), e.GetType().Name, e.Message); + Logger.LogError(LogTemplates.ExceptionLogTemplate, typeof(Scheduler).FullName, nameof(HandleStartupEvents), e.GetType().Name, e.Message); } } diff --git a/Core/Stateflows/Common/Scheduler/StartupExecutor.cs b/Core/Stateflows/Common/Scheduler/StartupExecutor.cs index 5e85dc15..def8b1be 100644 --- a/Core/Stateflows/Common/Scheduler/StartupExecutor.cs +++ b/Core/Stateflows/Common/Scheduler/StartupExecutor.cs @@ -15,7 +15,7 @@ internal class StartupExecutor private readonly IBehaviorLocator Locator; - private readonly ILogger Logger; + private readonly ILogger Logger; private readonly TimeSpan LockTimeout = new TimeSpan(0, 0, 10); @@ -28,7 +28,7 @@ public StartupExecutor( IStateflowsLock @lock, IBehaviorClassesProvider behaviorClassesProvider, IBehaviorLocator behaviorLocator, - ILogger logger + ILogger logger ) { Services = services; @@ -36,7 +36,7 @@ ILogger logger BehaviorClassesProvider = behaviorClassesProvider; Locator = behaviorLocator; Logger = logger; - HandlingLockId = new BehaviorId(nameof(ThreadScheduler), nameof(HandlingLockId), new BehaviorClass("", "").Environment); + HandlingLockId = new BehaviorId(nameof(Scheduler), nameof(HandlingLockId), new BehaviorClass("", "").Environment); } public async Task ExecuteAsync() diff --git a/Core/Stateflows/DependencyInjection.cs b/Core/Stateflows/DependencyInjection.cs index fed884e0..cfbc35cf 100644 --- a/Core/Stateflows/DependencyInjection.cs +++ b/Core/Stateflows/DependencyInjection.cs @@ -29,8 +29,7 @@ internal static IStateflowsBuilder EnsureStateflowServices(this IStateflowsBuild .AddSingleton() .AddHostedService(provider => provider.GetService()) .AddSingleton(provider => provider.GetService()) - .AddHostedService() - .AddHostedService() + .AddHostedService() .AddTransient() .AddTransient() .AddSingleton() diff --git a/Core/Stateflows/StateMachines/Engine/Executor.cs b/Core/Stateflows/StateMachines/Engine/Executor.cs index 24cacfc0..ec4f29e6 100644 --- a/Core/Stateflows/StateMachines/Engine/Executor.cs +++ b/Core/Stateflows/StateMachines/Engine/Executor.cs @@ -481,7 +481,6 @@ public async Task DoFinalizeStateMachineAsync() var context = new StateMachineActionContext(Context); await Inspector.BeforeStateMachineFinalizeAsync(context); - try { await Graph.Finalize.WhenAll(Context); diff --git a/Examples/Blazor/Server/Blazor.Server/Program.cs b/Examples/Blazor/Server/Blazor.Server/Program.cs index f2e379f1..5a398be3 100644 --- a/Examples/Blazor/Server/Blazor.Server/Program.cs +++ b/Examples/Blazor/Server/Blazor.Server/Program.cs @@ -34,9 +34,12 @@ builder.Services.AddStateflows(b => b .AddPlantUml() - .AddStorage() + //.AddStorage() + + .AddDefaultInstance(StateMachine.ToClass()) .AddStateMachines(b => b + .AddStateMachine() .AddStateMachine("stateMachine1", b => b //.AddExceptionHandler() //.AddInterceptor() @@ -86,12 +89,16 @@ ) .AddActivities(b => b - .AddActivity("clearing") + .AddActivity() .AddActivity("activity1", b => b - .AddAcceptEventAction(b => b - .AddControlFlow>() - ) - .AddAcceptEventAction(async c => Debug.WriteLine("Yuppi!")) + //.AddAcceptEventAction(b => b + // .AddControlFlow>() + //) + .AddAcceptEventAction(async c => + { + Debug.WriteLine("Yuppi!"); + throw new Exception("test"); + }) ) .AddActivity("activity2", b => b .AddInitializer(async c => @@ -198,6 +205,16 @@ namespace X { + [StateMachineBehavior(nameof(Default))] + public class Default : IStateMachine + { + public void Build(IStateMachineBuilder builder) + => builder + .AddInitialState("1", b => b + .AddOnEntry(async c => Debug.WriteLine("dupa")) + ); + } + public class Structured : IStructuredActivityNodeInitialization { public Task OnInitializeAsync() @@ -237,19 +254,31 @@ public class Activity3 : IActivity { public void Build(IActivityBuilder builder) => builder - .AddInitializer(async c => + .AddDefaultInitializer(async c => { - Debug.WriteLine(c.InitializationEvent.Foo); + //Debug.WriteLine(c.InitializationEvent.Foo); + + return true; }) .AddAcceptEventAction(async c => { }, b => b - .AddControlFlow("action1") + .AddControlFlow("action1", b => b + .AddGuard(async c => + { + throw new NotImplementedException(); + }) + ) ) .AddAction( "action1", - async c => c.OutputRange(Enumerable.Range(0, 100)), - b => b.AddFlow("chunked") + async c => + { + c.OutputRange(Enumerable.Range(0, 100)); + }, + b => b + .AddFlow("chunked") + //.AddExceptionHandler(async c => { }) ) .AddParallelActivity( "chunked", @@ -274,11 +303,11 @@ public void Build(IActivityBuilder builder) { lock (c.Activity.LockHandle) { - var counter = 0; - c.Activity.Values.TryGet("count", out counter); - - counter++; - c.Activity.Values.Set("count", counter); + if (c.Activity.Values.TryGet("count", out var counter)) + { + counter++; + c.Activity.Values.Set("count", counter); + } } return true; diff --git a/Locks/Stateflows.Locks.DistributedLock/Stateflows.Locks.DistributedLock.csproj b/Locks/Stateflows.Locks.DistributedLock/Stateflows.Locks.DistributedLock.csproj index 1e383126..ab3e9c1b 100644 --- a/Locks/Stateflows.Locks.DistributedLock/Stateflows.Locks.DistributedLock.csproj +++ b/Locks/Stateflows.Locks.DistributedLock/Stateflows.Locks.DistributedLock.csproj @@ -36,4 +36,8 @@ + + + + diff --git a/Storage/Stateflows.Storage.EntityFrameworkCore/Stateflows.Storage.EntityFrameworkCore.csproj b/Storage/Stateflows.Storage.EntityFrameworkCore/Stateflows.Storage.EntityFrameworkCore.csproj index 5ad630f5..0e2e2aa9 100644 --- a/Storage/Stateflows.Storage.EntityFrameworkCore/Stateflows.Storage.EntityFrameworkCore.csproj +++ b/Storage/Stateflows.Storage.EntityFrameworkCore/Stateflows.Storage.EntityFrameworkCore.csproj @@ -47,4 +47,8 @@ + + + + diff --git a/Transport/Stateflows.Transport.Http/Stateflows.Transport.Http.csproj b/Transport/Stateflows.Transport.Http/Stateflows.Transport.Http.csproj index 88f37d81..2165dc4b 100644 --- a/Transport/Stateflows.Transport.Http/Stateflows.Transport.Http.csproj +++ b/Transport/Stateflows.Transport.Http/Stateflows.Transport.Http.csproj @@ -36,4 +36,9 @@ \ + + + + +