2023-04-09 21:35:32 +00:00
using Elsa.Common.Models ;
using Elsa.Workflows.Core.Contracts ;
using Elsa.Workflows.Core.Models ;
2023-04-10 13:32:40 +00:00
using Elsa.Workflows.Core.Services ;
2023-04-09 21:35:32 +00:00
using Elsa.Workflows.Runtime.Contracts ;
using Elsa.Workflows.Runtime.Middleware.Activities ;
using Elsa.Workflows.Runtime.Models ;
using Elsa.Workflows.Runtime.Models.Requests ;
namespace Elsa.Workflows.Runtime.Services ;
/// <summary>
/// An activity invoker that invokes activities detached from the workflow. This is useful for invoking activities from a background worker.
/// </summary>
public class DefaultBackgroundActivityInvoker : IBackgroundActivityInvoker
{
private readonly IWorkflowRuntime _workflowRuntime ;
private readonly IWorkflowDispatcher _workflowDispatcher ;
private readonly IWorkflowDefinitionService _workflowDefinitionService ;
private readonly IWorkflowExecutionContextFactory _workflowExecutionContextFactory ;
private readonly IVariablePersistenceManager _variablePersistenceManager ;
private readonly IActivityInvoker _activityInvoker ;
private readonly IServiceProvider _serviceProvider ;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultBackgroundActivityInvoker"/> class.
/// </summary>
public DefaultBackgroundActivityInvoker (
IWorkflowRuntime workflowRuntime ,
IWorkflowDispatcher workflowDispatcher ,
IWorkflowDefinitionService workflowDefinitionService ,
IWorkflowExecutionContextFactory workflowExecutionContextFactory ,
IVariablePersistenceManager variablePersistenceManager ,
IActivityInvoker activityInvoker ,
IServiceProvider serviceProvider )
{
_workflowRuntime = workflowRuntime ;
_workflowDispatcher = workflowDispatcher ;
_workflowDefinitionService = workflowDefinitionService ;
_workflowExecutionContextFactory = workflowExecutionContextFactory ;
_variablePersistenceManager = variablePersistenceManager ;
_activityInvoker = activityInvoker ;
_serviceProvider = serviceProvider ;
}
/// <inheritdoc />
public async Task ExecuteAsync ( ScheduledBackgroundActivity scheduledBackgroundActivity , CancellationToken cancellationToken = default )
{
var workflowInstanceId = scheduledBackgroundActivity . WorkflowInstanceId ;
var workflowState = await _workflowRuntime . ExportWorkflowStateAsync ( workflowInstanceId , cancellationToken ) ;
if ( workflowState = = null )
throw new Exception ( "Workflow state not found" ) ;
var workflowDefinition = await _workflowDefinitionService . FindAsync ( workflowState . DefinitionId , VersionOptions . SpecificVersion ( workflowState . DefinitionVersion ) , cancellationToken ) ;
if ( workflowDefinition = = null )
throw new Exception ( "Workflow definition not found" ) ;
var workflow = await _workflowDefinitionService . MaterializeWorkflowAsync ( workflowDefinition , cancellationToken ) ;
var workflowExecutionContext = await _workflowExecutionContextFactory . CreateAsync ( _serviceProvider , workflow , workflowState . Id , workflowState , cancellationToken : cancellationToken ) ;
2023-04-13 18:05:14 +00:00
var activityNodeId = scheduledBackgroundActivity . ActivityNodeId ;
var activityExecutionContext = workflowExecutionContext . ActivityExecutionContexts . First ( x = > x . NodeId = = activityNodeId ) ;
2023-04-09 21:35:32 +00:00
// Load persistent variables for the activity to use.
await _variablePersistenceManager . LoadVariablesAsync ( workflowExecutionContext ) ;
// Mark the activity as being invoked from a background worker.
activityExecutionContext . TransientProperties [ BackgroundActivityInvokerMiddleware . IsBackgroundExecution ] = true ;
// Invoke the activity.
await _activityInvoker . InvokeAsync ( activityExecutionContext ) ;
2023-04-10 13:32:40 +00:00
// Capture any activity output produced by the activity (but only if the associated memory block is stored in the workflow itself).
2023-04-09 21:35:32 +00:00
var outputDescriptors = activityExecutionContext . ActivityDescriptor . Outputs ;
var outputValues = new Dictionary < string , object > ( ) ;
foreach ( var outputDescriptor in outputDescriptors )
{
var output = ( Output ? ) outputDescriptor . ValueGetter ( activityExecutionContext . Activity ) ;
2023-04-10 13:32:40 +00:00
if ( output = = null )
continue ;
var memoryBlockReference = output . MemoryBlockReference ( ) ;
if ( ! activityExecutionContext . ExpressionExecutionContext . TryGetBlock ( memoryBlockReference , out var memoryBlock ) )
continue ;
var variableMetadata = memoryBlock . Metadata as VariableBlockMetadata ;
var driver = variableMetadata ? . StorageDriverType ;
// We only capture output written to the workflow itself. Other drivers like blob storage, etc. will be ignored since the foreground context will be loading those.
if ( driver ! = typeof ( WorkflowStorageDriver ) )
continue ;
var outputValue = activityExecutionContext . Get ( memoryBlockReference ) ;
2023-04-09 21:35:32 +00:00
if ( outputValue ! = null )
outputValues [ outputDescriptor . Name ] = outputValue ;
}
2023-04-10 13:32:40 +00:00
// Persist any variables that were written to by the activity.
await _variablePersistenceManager . SaveVariablesAsync ( workflowExecutionContext ) ;
2023-04-09 21:35:32 +00:00
// Resume the workflow, passing along the activity output.
// TODO: This approach will fail if the output is non-serializable. We need to find a way to pass the output to the workflow without serializing it.
var bookmarkId = scheduledBackgroundActivity . BookmarkId ;
2023-04-13 18:05:14 +00:00
var inputKey = BackgroundActivityInvokerMiddleware . GetBackgroundActivityOutputKey ( activityNodeId ) ;
2023-04-09 21:35:32 +00:00
var dispatchRequest = new DispatchWorkflowInstanceRequest
{
InstanceId = workflowInstanceId ,
BookmarkId = bookmarkId ,
Input = new Dictionary < string , object >
{
[inputKey] = outputValues
}
} ;
await _workflowDispatcher . DispatchAsync ( dispatchRequest , cancellationToken ) ;
}
}