< Summary

Information
Class: Elsa.Workflows.Runtime.Services.DrainTriggerExecutor
Assembly: Elsa.Workflows.Runtime
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Services/DrainTriggerExecutor.cs
Line coverage
100%
Covered lines: 13
Uncovered lines: 0
Coverable lines: 13
Total lines: 63
Line coverage: 100%
Branch coverage
100%
Covered branches: 6
Total branches: 6
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
RunAsync()100%66100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Services/DrainTriggerExecutor.cs

#LineLine coverage
 1using Microsoft.Extensions.Logging;
 2
 3namespace 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>
 19internal 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        {
 1042            var outcome = await orchestrator.DrainAsync(trigger, cancellationToken);
 743            if (outcome.OverallResult is DrainResult.DeadlineExceeded or DrainResult.AbortedByUnhandledException)
 44            {
 145                logger.LogWarning(
 146                    "{Context} finished with non-clean result: {Result} (forceCancelled={Count}).",
 147                    contextLabel, outcome.OverallResult, outcome.ExecutionCyclesForceCancelledCount);
 48            }
 49            else
 50            {
 651                logger.LogInformation(
 652                    "{Context} finished: {Result} (paused={Paused}, waited={Waited}).",
 653                    contextLabel, outcome.OverallResult, outcome.PausePhaseDuration, outcome.WaitPhaseDuration);
 54            }
 755        }
 356        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.
 360            logger.LogInformation("{Context} skipped: {Reason}", contextLabel, ex.Message);
 361        }
 1062    }
 63}

Methods/Properties

RunAsync()