| | | 1 | | using Microsoft.Extensions.Logging; |
| | | 2 | | |
| | | 3 | | namespace Elsa.Workflows.Runtime.Services; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// Shared invocation shell for drain triggers (host stop, shell deactivation, etc.). Calls |
| | | 7 | | /// <see cref="IDrainOrchestrator.DrainAsync"/> with a uniform Information/Warning logging |
| | | 8 | | /// pattern and suppresses the parallel-drain <see cref="InvalidOperationException"/> the |
| | | 9 | | /// orchestrator throws when another trigger has already run for this generation. |
| | | 10 | | /// </summary> |
| | | 11 | | /// <remarks> |
| | | 12 | | /// Both <c>ElsaShellDrainHandler</c> (CShells <c>IDrainHandler</c>) and |
| | | 13 | | /// <c>DrainOrchestratorHostedService</c> (.NET <c>IHostedService.StopAsync</c>) used to inline |
| | | 14 | | /// near-identical try/catch/log shapes. They had already drifted (host-stop's success log |
| | | 15 | | /// omitted the <c>paused</c>/<c>waited</c> durations the shell-handler version included, and |
| | | 16 | | /// the "skipped" message disagreed on the trigger label). Centralising the shape here keeps |
| | | 17 | | /// the two — and any future trigger source — uniform. |
| | | 18 | | /// </remarks> |
| | | 19 | | internal static class DrainTriggerExecutor |
| | | 20 | | { |
| | | 21 | | /// <summary> |
| | | 22 | | /// Runs a drain for the supplied trigger and logs the outcome. |
| | | 23 | | /// </summary> |
| | | 24 | | /// <param name="orchestrator">The drain orchestrator to invoke.</param> |
| | | 25 | | /// <param name="trigger">Which trigger source initiated the drain.</param> |
| | | 26 | | /// <param name="logger">Logger to emit the outcome under (the caller's category).</param> |
| | | 27 | | /// <param name="contextLabel"> |
| | | 28 | | /// Human-readable label used verbatim in the log messages (e.g. <c>"Shell drain"</c>, |
| | | 29 | | /// <c>"Graceful drain"</c>) so operators can tell at a glance which trigger produced an |
| | | 30 | | /// entry without inspecting the log category. |
| | | 31 | | /// </param> |
| | | 32 | | /// <param name="cancellationToken">Cancellation propagated to the orchestrator.</param> |
| | | 33 | | public static async Task RunAsync( |
| | | 34 | | IDrainOrchestrator orchestrator, |
| | | 35 | | DrainTrigger trigger, |
| | | 36 | | ILogger logger, |
| | | 37 | | string contextLabel, |
| | | 38 | | CancellationToken cancellationToken) |
| | | 39 | | { |
| | | 40 | | try |
| | | 41 | | { |
| | 10 | 42 | | var outcome = await orchestrator.DrainAsync(trigger, cancellationToken); |
| | 7 | 43 | | if (outcome.OverallResult is DrainResult.DeadlineExceeded or DrainResult.AbortedByUnhandledException) |
| | | 44 | | { |
| | 1 | 45 | | logger.LogWarning( |
| | 1 | 46 | | "{Context} finished with non-clean result: {Result} (forceCancelled={Count}).", |
| | 1 | 47 | | contextLabel, outcome.OverallResult, outcome.ExecutionCyclesForceCancelledCount); |
| | | 48 | | } |
| | | 49 | | else |
| | | 50 | | { |
| | 6 | 51 | | logger.LogInformation( |
| | 6 | 52 | | "{Context} finished: {Result} (paused={Paused}, waited={Waited}).", |
| | 6 | 53 | | contextLabel, outcome.OverallResult, outcome.PausePhaseDuration, outcome.WaitPhaseDuration); |
| | | 54 | | } |
| | 7 | 55 | | } |
| | 3 | 56 | | catch (InvalidOperationException ex) |
| | | 57 | | { |
| | | 58 | | // Parallel non-force drain rejected by the orchestrator — another trigger already drained |
| | | 59 | | // this generation. Safe to swallow; the original drain's outcome stands. |
| | 3 | 60 | | logger.LogInformation("{Context} skipped: {Reason}", contextLabel, ex.Message); |
| | 3 | 61 | | } |
| | 10 | 62 | | } |
| | | 63 | | } |