public async Task <CancelWorkflowInstanceResult> CancelAsync(string workflowInstanceId, CancellationToken cancellationToken = default)
        {
            var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken);

            if (workflowInstance == null)
            {
                return(new CancelWorkflowInstanceResult(CancelWorkflowInstanceResultStatus.NotFound, null));
            }

            if (workflowInstance.WorkflowStatus != WorkflowStatus.Idle && workflowInstance.WorkflowStatus != WorkflowStatus.Running && workflowInstance.WorkflowStatus != WorkflowStatus.Suspended)
            {
                return(new CancelWorkflowInstanceResult(CancelWorkflowInstanceResultStatus.InvalidStatus, workflowInstance));
            }

            workflowInstance.BlockingActivities = new HashSet <BlockingActivity>();
            workflowInstance.CurrentActivity    = null;
            workflowInstance.WorkflowStatus     = WorkflowStatus.Cancelled;
            workflowInstance.CancelledAt        = _clock.GetCurrentInstant();

            await _workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken);

            await _mediator.Publish(new WorkflowInstanceCancelled(workflowInstance), cancellationToken);

            return(new CancelWorkflowInstanceResult(CancelWorkflowInstanceResultStatus.Ok, workflowInstance));
        }
示例#2
0
        public async Task ExecuteAsync(CancellationToken cancellationToken = default)
        {
            await using var handle = await _distributedLockProvider.AcquireLockAsync(GetType ().Name, _elsaOptions.DistributedLockTimeout, cancellationToken);

            if (handle == null)
            {
                return;
            }

            var instances = await _workflowInstanceStore.FindManyAsync(new WorkflowStatusSpecification(WorkflowStatus.Running), cancellationToken : cancellationToken).ToList();

            if (instances.Any())
            {
                _logger.LogInformation("Found {WorkflowInstanceCount} workflows with status 'Running'. Resuming each one of them", instances.Count);
            }
            else
            {
                _logger.LogInformation("Found no workflows with status 'Running'. Nothing to resume");
            }

            foreach (var instance in instances)
            {
                await using var correlationLockHandle = await _distributedLockProvider.AcquireLockAsync(instance.CorrelationId, _elsaOptions.DistributedLockTimeout, cancellationToken);

                if (handle == null)
                {
                    _logger.LogWarning("Failed to acquire lock on correlation {CorrelationId} for workflow instance {WorkflowInstanceId}", instance.CorrelationId, instance.Id);
                    continue;
                }

                _logger.LogInformation("Resuming {WorkflowInstanceId}", instance.Id);
                var scheduledActivities = instance.ScheduledActivities;

                if (instance.CurrentActivity == null && !scheduledActivities.Any())
                {
                    if (instance.BlockingActivities.Any())
                    {
                        _logger.LogWarning(
                            "Workflow '{WorkflowInstanceId}' was in the Running state, but has no scheduled activities not has a currently executing one. However, it does have blocking activities, so switching to Suspended status",
                            instance.Id);

                        instance.WorkflowStatus = WorkflowStatus.Suspended;
                        await _workflowInstanceStore.SaveAsync(instance, cancellationToken);

                        continue;
                    }

                    _logger.LogWarning("Workflow '{WorkflowInstanceId}' was in the Running state, but has no scheduled activities nor has a currently executing one", instance.Id);
                    continue;
                }

                var scheduledActivity = instance.CurrentActivity ?? instance.ScheduledActivities.Peek();

                await _workflowInstanceDispatcher.DispatchAsync(new ExecuteWorkflowInstanceRequest(instance.Id, scheduledActivity.ActivityId), cancellationToken);
            }
        }
示例#3
0
        protected override async ValueTask <IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
        {
            // Persist the workflow before sending the signal. This fixes a use case where a responding workflow sends a response signal handled by this workflow in a separate branch for example.
            await _workflowInstanceStore.SaveAsync(context.WorkflowInstance, context.CancellationToken);

            // Trigger the signal synchronously. If we dispatched the signal instead, we don;t have to explicitly save the workflow instance. For future reconsideration.
            await _signaler.DispatchSignalAsync(Signal, Input, correlationId : CorrelationId, cancellationToken : context.CancellationToken);

            return(Done());
        }
示例#4
0
        private async Task <StartableWorkflow> InstantiateStartableWorkflow(StartableWorkflowDefinition startableWorkflowDefinition, CancellationToken cancellationToken)
        {
            var workflowInstance = await _workflowFactory.InstantiateAsync(
                startableWorkflowDefinition.WorkflowBlueprint,
                startableWorkflowDefinition.CorrelationId,
                startableWorkflowDefinition.ContextId,
                cancellationToken : cancellationToken);

            await _workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken);

            return(new StartableWorkflow(startableWorkflowDefinition.WorkflowBlueprint, workflowInstance, startableWorkflowDefinition.ActivityId));
        }
示例#5
0
        private async ValueTask SaveWorkflowAsync(WorkflowInstance workflowInstance, CancellationToken cancellationToken)
        {
            _logger.LogTrace("Persisting workflow instance {WorkflowInstanceId}", workflowInstance.Id);

            // Can't prune data - we need to figure out a better way to remove activity output data.
            // Doing it right now causes issues when transferring output data from composite activities.
            //workflowExecutionContext.PruneActivityData();

            await _workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken);

            _logger.LogDebug("Committed workflow {WorkflowInstanceId} to storage", workflowInstance.Id);
        }
 public async Task Handle(WorkflowSuspended notification, CancellationToken cancellationToken)
 {
     try
     {
         await notification.WorkflowExecutionContext.ProcessRegisteredTasksAsync(cancellationToken);
     }
     catch (Exception e)
     {
         notification.WorkflowExecutionContext.Fault(e, "Error occurred while executing post-suspension task", null, null, false);
         await _workflowInstanceStore.SaveAsync(notification.WorkflowExecutionContext.WorkflowInstance, cancellationToken);
     }
 }
        private async Task <StartableWorkflow?> CollectStartableWorkflowInternalAsync(
            IWorkflowBlueprint workflowBlueprint,
            string?activityId,
            string correlationId,
            string?contextId = default,
            string?tenantId  = default,
            CancellationToken cancellationToken = default)
        {
            var workflowDefinitionId = workflowBlueprint.Id;

            if (!ValidatePreconditions(workflowDefinitionId, workflowBlueprint))
            {
                return(null);
            }

            // Acquire a lock on the workflow definition so that we can ensure singleton-workflows never execute more than one instance.
            var lockKey = $"execute-workflow-definition:tenant:{tenantId}:workflow-definition:{workflowDefinitionId}";

            await using var workflowDefinitionHandle = await AcquireLockAsync(lockKey, cancellationToken);

            if (workflowBlueprint.IsSingleton)
            {
                if (await GetWorkflowIsAlreadyExecutingAsync(tenantId, workflowDefinitionId))
                {
                    _logger.LogDebug("Workflow {WorkflowDefinitionId} is a singleton workflow and is already running", workflowDefinitionId);
                    return(null);
                }
            }

            var startActivities = _getsStartActivities.GetStartActivities(workflowBlueprint).Select(x => x.Id).ToHashSet();
            var startActivityId = activityId == null?startActivities.FirstOrDefault() : startActivities.Contains(activityId) ? activityId : default;

            if (startActivityId == null)
            {
                _logger.LogWarning("Cannot start workflow {WorkflowDefinitionId} with version {WorkflowDefinitionVersion} because it has no starting activities", workflowBlueprint.Id, workflowBlueprint.Version);
                return(null);
            }

            var workflowInstance = await _workflowFactory.InstantiateAsync(
                workflowBlueprint,
                correlationId,
                contextId,
                cancellationToken);

            await _workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken);

            return(new StartableWorkflow(workflowBlueprint, workflowInstance, startActivityId));
        }
示例#8
0
        public async Task <WorkflowInstance> StartWorkflowAsync(
            IWorkflowBlueprint workflowBlueprint,
            string?activityId    = default,
            object?input         = default,
            string?correlationId = default,
            string?contextId     = default,
            CancellationToken cancellationToken = default)
        {
            var workflowInstance = await _workflowFactory.InstantiateAsync(
                workflowBlueprint,
                correlationId,
                contextId,
                cancellationToken);

            await _workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken);

            return(await _workflowRunner.RunWorkflowAsync(workflowBlueprint, workflowInstance, activityId, input, cancellationToken));
        }
示例#9
0
        public async Task <RunWorkflowResult> StartWorkflowAsync(
            IWorkflowBlueprint workflowBlueprint,
            string?activityId    = default,
            WorkflowInput?input  = default,
            string?correlationId = default,
            string?contextId     = default,
            string?tenantId      = default,
            CancellationToken cancellationToken = default)
        {
            var workflowInstance = await _workflowFactory.InstantiateAsync(
                workflowBlueprint,
                correlationId,
                contextId,
                tenantId,
                cancellationToken);

            await _workflowStorageService.UpdateInputAsync(workflowInstance, input, cancellationToken);

            await _workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken);

            return(await _workflowRunner.RunWorkflowAsync(workflowBlueprint, workflowInstance, activityId, cancellationToken));
        }
示例#10
0
        public async Task <WorkflowInstance> ReviveAsync(WorkflowInstance workflowInstance, CancellationToken cancellationToken)
        {
            if (workflowInstance.WorkflowStatus != WorkflowStatus.Faulted)
            {
                throw new InvalidOperationException($"Cannot revive non-faulted workflow with status {workflowInstance.WorkflowStatus}");
            }

            var fault = workflowInstance.Fault;

            if (fault == null)
            {
                throw new WorkflowException("Cannot revive a workflow with no fault");
            }

            var faultedActivityId = fault.FaultedActivityId;

            if (faultedActivityId == null)
            {
                // The workflow failed before or after running an activity.
                // If no activities were scheduled, the workflow faulted before finishing executing the first activity.
                var hasScheduledActivities = workflowInstance.CurrentActivity != null;
                workflowInstance.WorkflowStatus = !hasScheduledActivities ? WorkflowStatus.Idle : fault.Resuming ? WorkflowStatus.Suspended : WorkflowStatus.Running;
            }
            else
            {
                // An activity caused the fault.
                workflowInstance.WorkflowStatus = fault.Resuming ? WorkflowStatus.Suspended : WorkflowStatus.Running;
                workflowInstance.ScheduledActivities.Push(new ScheduledActivity(faultedActivityId, fault.ActivityInput));
            }

            workflowInstance.Fault     = null;
            workflowInstance.FaultedAt = null;
            await _workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken);

            return(workflowInstance);
        }
示例#11
0
        protected override async ValueTask <IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
        {
            // Persist the workflow before sending the signal. This fixes a use case where a responding workflow sends a response signal handled by this workflow in a separate branch for example.
            await _workflowInstanceStore.SaveAsync(context.WorkflowInstance, context.CancellationToken);

            // Trigger the signal synchronously. If we dispatched the signal instead, we don't have to explicitly save the workflow instance. For future reconsideration.
            // Warning: Sending a signal directly instead of dispatching it can potentially cause a deadlock if another workflow sends a signal back to this workflow immediately.

            switch (SendMode)
            {
            case SendSignalMode.Synchronously:
                await _signaler.TriggerSignalAsync(Signal, Input, correlationId : CorrelationId, cancellationToken : context.CancellationToken);

                break;

            case SendSignalMode.Asynchronously:
            default:
                await _signaler.DispatchSignalAsync(Signal, Input, correlationId : CorrelationId, cancellationToken : context.CancellationToken);

                break;
            }

            return(Done());
        }
        public async Task SaveAsync(WorkflowInstance entity, CancellationToken cancellationToken = default)
        {
            await _store.SaveAsync(entity, cancellationToken);

            await _mediator.Publish(new WorkflowInstanceSaved(entity), cancellationToken);
        }
示例#13
0
        public async Task <StartableWorkflow?> CollectStartableWorkflowAsync(IWorkflowBlueprint workflowBlueprint, string?activityId, string?correlationId = default, string?contextId = default, string?tenantId = default, CancellationToken cancellationToken = default)
        {
            var workflowDefinitionId = workflowBlueprint.Id;

            if (!ValidatePreconditions(workflowDefinitionId, workflowBlueprint))
            {
                return(null);
            }

            var correlationLockHandle = default(IDistributedSynchronizationHandle?);

            // If we are creating a correlated workflow, make sure to acquire a lock on it to prevent duplicate workflow instances from being created.
            if (!string.IsNullOrWhiteSpace(correlationId))
            {
                _logger.LogDebug("Acquiring lock on correlation ID {CorrelationId}", correlationId);
                correlationLockHandle = await _distributedLockProvider.AcquireLockAsync(correlationId, _elsaOptions.DistributedLockTimeout, cancellationToken);

                if (correlationLockHandle == null)
                {
                    throw new LockAcquisitionException($"Failed to acquire a lock on correlation ID {correlationId}");
                }
            }

            try
            {
                // Acquire a lock on the workflow definition so that we can ensure singleton-workflows never execute more than one instance.
                var lockKey = $"execute-workflow-definition:tenant:{tenantId}:workflow-definition:{workflowDefinitionId}";
                await using var handle = await _distributedLockProvider.AcquireLockAsync(lockKey, _elsaOptions.DistributedLockTimeout, cancellationToken);

                if (handle == null)
                {
                    throw new LockAcquisitionException($"Failed to acquire a lock on {lockKey}");
                }

                if (!workflowBlueprint !.IsSingleton || await GetWorkflowIsAlreadyExecutingAsync(tenantId, workflowDefinitionId) == false)
                {
                    var startActivities = _getsStartActivities.GetStartActivities(workflowBlueprint).Select(x => x.Id).ToHashSet();
                    var startActivityId = activityId == null?startActivities.FirstOrDefault() : startActivities.Contains(activityId) ? activityId : default;

                    if (startActivityId == null)
                    {
                        _logger.LogWarning("Cannot start workflow {WorkflowDefinitionId} with version {WorkflowDefinitionVersion} because it has no starting activities", workflowBlueprint.Id, workflowBlueprint.Version);
                        return(null);
                    }

                    var workflowInstance = await _workflowFactory.InstantiateAsync(
                        workflowBlueprint,
                        correlationId,
                        contextId,
                        cancellationToken);

                    await _workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken);

                    return(new StartableWorkflow(workflowBlueprint, workflowInstance, startActivityId));
                }
            }
            finally
            {
                if (correlationLockHandle != null)
                {
                    await correlationLockHandle.DisposeAsync();
                }
            }

            return(null);
        }
示例#14
0
 private async Task SaveWorkflowAsync(Workflow workflow, CancellationToken cancellationToken)
 {
     var workflowInstance = workflow.ToInstance();
     await workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken);
 }