using Elsa.Workflows.CommitStates; using Elsa.Workflows.Runtime.Middleware.Workflows; using Elsa.Workflows.State; namespace Elsa.Workflows.Runtime.Services; /// /// Decorator that disposes the execution-cycle handle stored in /// AFTER the inner commit handler has persisted the /// workflow's terminal state. This ordering is essential for the drain orchestrator's force-cancel path: the /// orchestrator awaits with a settle timeout before it overwrites the /// persisted sub-status with . Without this decorator the handle would /// dispose at the end of the pipeline middleware (i.e., BEFORE commit), the orchestrator's wait would complete /// prematurely, and the orchestrator's Interrupted write would either find no instance row (early dispatches) /// or be clobbered by the runner's subsequent commit (mid-execution dispatches). /// public sealed class ExecutionCycleAwareCommitStateHandler : ICommitStateHandler { private readonly DefaultCommitStateHandler _inner; public ExecutionCycleAwareCommitStateHandler(DefaultCommitStateHandler inner) => _inner = inner; /// public async Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken = default) { try { await _inner.CommitAsync(workflowExecutionContext, cancellationToken); } finally { DisposeCycleHandleIfPresent(workflowExecutionContext); } } /// public async Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowState workflowState, CancellationToken cancellationToken = default) { try { await _inner.CommitAsync(workflowExecutionContext, workflowState, cancellationToken); } finally { DisposeCycleHandleIfPresent(workflowExecutionContext); } } private static void DisposeCycleHandleIfPresent(WorkflowExecutionContext context) { if (context.TransientProperties.TryGetValue(ExecutionCycleTrackingMiddleware.ExecutionCycleHandleKey, out var raw) && raw is ExecutionCycleHandle handle) handle.Dispose(); } }