< Summary

Information
Class: Elsa.Workflows.Runtime.WorkflowActivationGate
Assembly: Elsa.Workflows.Runtime
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Services/WorkflowActivationGate.cs
Line coverage
100%
Covered lines: 62
Uncovered lines: 0
Coverable lines: 62
Total lines: 125
Line coverage: 100%
Branch coverage
94%
Covered branches: 32
Total branches: 34
Branch coverage: 94.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
EvaluateAsync()85.71%1414100%
GetLockKey(...)88.88%1818100%
CreateLockKey(...)100%22100%

File(s)

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

#LineLine coverage
 1using System.Buffers.Binary;
 2using System.Security.Cryptography;
 3using System.Text;
 4using Elsa.Common.DistributedHosting;
 5using Elsa.Common.Multitenancy;
 6using Elsa.Workflows.ActivationValidators;
 7using Elsa.Workflows.Activities;
 8using Elsa.Workflows.Runtime.ActivationValidators;
 9using Medallion.Threading;
 10using Microsoft.Extensions.Options;
 11
 12namespace Elsa.Workflows.Runtime;
 13
 14/// <inheritdoc />
 20215public class WorkflowActivationGate(
 20216    IWorkflowActivationStrategyEvaluator evaluator,
 20217    IDistributedLockProvider distributedLockProvider,
 20218    IOptions<DistributedLockingOptions> distributedLockingOptions,
 20219    ITenantAccessor? tenantAccessor = null) : IWorkflowActivationGate
 20{
 21    /// <inheritdoc />
 22    public async Task<WorkflowActivationLease> EvaluateAsync(Workflow workflow, string? correlationId, CancellationToken
 23    {
 19024        var lockKey = GetLockKey(workflow, correlationId);
 19025        IDistributedSynchronizationHandle? lockHandle = null;
 19026        CancellationTokenSource? linkedCancellationTokenSource = null;
 27
 19028        if (lockKey != null)
 3729            lockHandle = await distributedLockProvider.AcquireLockAsync(lockKey, distributedLockingOptions.Value.LockAcq
 30
 31        try
 32        {
 18933            var effectiveCancellationToken = cancellationToken;
 18934            if (lockHandle != null)
 35            {
 3636                linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, lockH
 3637                effectiveCancellationToken = linkedCancellationTokenSource.Token;
 38            }
 39
 18940            effectiveCancellationToken.ThrowIfCancellationRequested();
 18941            var canStart = await evaluator.CanStartWorkflowAsync(new()
 18942            {
 18943                Workflow = workflow,
 18944                CorrelationId = correlationId,
 18945                CancellationToken = effectiveCancellationToken
 18946            });
 18547            effectiveCancellationToken.ThrowIfCancellationRequested();
 48
 18449            if (!canStart)
 50            {
 851                var acquiredLockHandle = lockHandle;
 852                lockHandle = null;
 853                if (acquiredLockHandle != null)
 854                    await acquiredLockHandle.DisposeAsync();
 55
 856                linkedCancellationTokenSource?.Dispose();
 857                linkedCancellationTokenSource = null;
 58
 859                return WorkflowActivationLease.Denied;
 60            }
 61
 17662            var lease = new WorkflowActivationLease(true, lockHandle, effectiveCancellationToken, linkedCancellationToke
 17663            lockHandle = null;
 17664            linkedCancellationTokenSource = null;
 17665            return lease;
 66        }
 567        catch
 68        {
 69            try
 70            {
 571                if (lockHandle != null)
 572                    await lockHandle.DisposeAsync();
 573            }
 74            finally
 75            {
 576                linkedCancellationTokenSource?.Dispose();
 77            }
 78
 579            throw;
 80        }
 18481    }
 82
 83    private string? GetLockKey(Workflow workflow, string? correlationId)
 84    {
 19085        var strategyType = workflow.Options.ActivationStrategyType;
 86
 19087        if (strategyType == null || strategyType == typeof(AllowAlwaysStrategy))
 15288            return null;
 89
 3890        var tenantId = tenantAccessor?.TenantId ?? Tenant.DefaultTenantId;
 3891        var definitionId = workflow.Identity.DefinitionId;
 92
 3893        if (strategyType == typeof(SingletonStrategy))
 1294            return CreateLockKey("singleton", tenantId, definitionId);
 95
 2696        if (strategyType == typeof(CorrelatedSingletonStrategy))
 97        {
 2098            return CreateLockKey("correlated-singleton", tenantId, definitionId, correlationId ?? string.Empty);
 99        }
 100
 6101        if (strategyType == typeof(CorrelationStrategy))
 102        {
 5103            return CreateLockKey("correlation", tenantId, correlationId ?? string.Empty);
 104        }
 105
 106        // Custom strategies remain compatible, but their uniqueness scope is unknown. Do not
 107        // invent a lock key that implies atomicity the strategy has not declared.
 1108        return null;
 109    }
 110
 111    private static string CreateLockKey(string strategy, params string[] components)
 112    {
 37113        using var stream = new MemoryStream();
 37114        Span<byte> length = stackalloc byte[sizeof(int)];
 356115        foreach (var bytes in components.Select(component => Encoding.UTF8.GetBytes(component)))
 116        {
 94117            BinaryPrimitives.WriteInt32BigEndian(length, bytes.Length);
 94118            stream.Write(length);
 94119            stream.Write(bytes);
 120        }
 121
 37122        var hash = Convert.ToHexString(SHA256.HashData(stream.ToArray()));
 37123        return $"workflow-activation:v1:{strategy}:{hash}";
 37124    }
 125}