< Summary

Information
Class: Elsa.Bpmn.Hosting.BpmnScopeDispatcher
Assembly: Elsa.Bpmn
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Bpmn/Hosting/BpmnScopeDispatcher.cs
Line coverage
100%
Covered lines: 14
Uncovered lines: 0
Coverable lines: 14
Total lines: 60
Line coverage: 100%
Branch coverage
100%
Covered branches: 4
Total branches: 4
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
get_IsDraining()100%11100%
PostAsync()100%44100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Bpmn/Hosting/BpmnScopeDispatcher.cs

#LineLine coverage
 1namespace Elsa.Bpmn.Hosting;
 2
 3/// <summary>
 4/// Runs BPMN scope evaluations one at a time, in arrival order, for one workflow instance.
 5/// </summary>
 6/// <remarks>
 7/// <para>
 8/// A nested scope can terminalize while its parent is still applying a command list, and a scope raising an
 9/// escalation delivers it to its parent from inside its own command loop. Either way the parent must not be
 10/// evaluated on the spot: doing so re-enters the interpreter with a half-applied live-work set and an evaluation
 11/// already in flight. Posting instead of calling is what keeps that from happening — a post made while the queue is
 12/// draining is appended and picked up once the evaluation in flight has fully applied.
 13/// </para>
 14/// <para>
 15/// One queue serves every scope in the instance, which is what makes the ordering total rather than per-scope.
 16/// It lives in the workflow execution context's transient properties: it coordinates a single burst of execution
 17/// and has nothing to persist.
 18/// </para>
 19/// </remarks>
 20internal sealed class BpmnScopeDispatcher
 21{
 5822    private readonly Queue<Func<ValueTask>> _queue = new();
 23
 24    private bool _draining;
 25
 26    /// <summary>Whether an evaluation is in flight, so a post will be queued rather than run.</summary>
 127    public bool IsDraining => _draining;
 28
 29    /// <summary>
 30    /// Queues an evaluation and, unless one is already in flight, drains the queue to exhaustion.
 31    /// </summary>
 32    public async ValueTask PostAsync(Func<ValueTask> evaluation)
 33    {
 14234        _queue.Enqueue(evaluation);
 35
 14236        if (_draining)
 1537            return;
 38
 12739        _draining = true;
 40
 41        try
 42        {
 26543            while (_queue.Count > 0)
 14144                await _queue.Dequeue()();
 12445        }
 346        catch
 47        {
 48            // An evaluation that threw may have applied part of a command list, so everything queued behind it was
 49            // computed against a state that no longer describes this instance. Dropping the rest is the conservative
 50            // direction: the failure is on its way to the incident strategy, and resuming half-planned work would
 51            // bury it under activity nobody asked for.
 352            _queue.Clear();
 353            throw;
 54        }
 55        finally
 56        {
 12757            _draining = false;
 58        }
 13959    }
 60}

Methods/Properties

.ctor()
get_IsDraining()
PostAsync()