< Summary

Information
Class: Elsa.Workflows.ActivityCompletionCallbackEntry
Assembly: Elsa.Workflows.Core
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 720
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Owner()100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs

#LineLine coverage
 1using System.Collections.ObjectModel;
 2using Elsa.Common;
 3using Elsa.Expressions.Helpers;
 4using Elsa.Expressions.Models;
 5using Elsa.Extensions;
 6using Elsa.Workflows.Activities;
 7using Elsa.Workflows.CommitStates;
 8using Elsa.Workflows.Exceptions;
 9using Elsa.Workflows.Helpers;
 10using Elsa.Workflows.Memory;
 11using Elsa.Workflows.Models;
 12using Elsa.Workflows.Options;
 13using Elsa.Workflows.State;
 14using JetBrains.Annotations;
 15using Microsoft.Extensions.DependencyInjection;
 16
 17namespace Elsa.Workflows;
 18
 19/// <summary>
 20/// A delegate entry that is used by activities to be notified when the activities they scheduled are completed.
 21/// </summary>
 22/// <param name="Owner">The activity scheduling the <see cref="Child"/> activity.</param>
 23/// <param name="Child">The child <see cref="IActivity"/> being scheduled.</param>
 24/// <param name="CompletionCallback">The <see cref="ActivityCompletionCallback"/> delegate to invoke when the scheduled 
 25/// <param name="Tag">An optional tag.</param>
 3802626public record ActivityCompletionCallbackEntry(ActivityExecutionContext Owner, ActivityNode Child, ActivityCompletionCall
 27
 28/// <summary>
 29/// Provides context to the currently executing workflow.
 30/// </summary>
 31[PublicAPI]
 32public partial class WorkflowExecutionContext : IExecutionContext
 33{
 34    private static readonly object ActivityOutputRegistryKey = new();
 35    private static readonly object LastActivityResultKey = new();
 36    internal static ValueTask Complete(ActivityExecutionContext context) => context.CompleteActivityAsync();
 37    internal static ValueTask Noop(ActivityExecutionContext context) => default;
 38    private readonly IList<ActivityCompletionCallbackEntry> _completionCallbackEntries = new List<ActivityCompletionCall
 39    private IList<ActivityExecutionContext> _activityExecutionContexts;
 40    private readonly IHasher _hasher;
 41    private readonly ICommitStateHandler _commitStateHandler;
 42
 43    /// <summary>
 44    /// Initializes a new instance of <see cref="WorkflowExecutionContext"/>.
 45    /// </summary>
 46    private WorkflowExecutionContext(
 47        IServiceProvider serviceProvider,
 48        WorkflowGraph workflowGraph,
 49        string id,
 50        string? correlationId,
 51        string? parentWorkflowInstanceId,
 52        IDictionary<string, object>? input,
 53        IDictionary<string, object>? properties,
 54        ExecuteActivityDelegate? executeDelegate,
 55        string? triggerActivityId,
 56        IEnumerable<ActivityIncident> incidents,
 57        IEnumerable<Bookmark> originalBookmarks,
 58        DateTimeOffset createdAt,
 59        CancellationToken cancellationToken)
 60    {
 61        ServiceProvider = serviceProvider;
 62        SystemClock = serviceProvider.GetRequiredService<ISystemClock>();
 63        ActivityRegistry = serviceProvider.GetRequiredService<IActivityRegistry>();
 64        ActivityRegistryLookup = serviceProvider.GetRequiredService<IActivityRegistryLookupService>();
 65        _hasher = serviceProvider.GetRequiredService<IHasher>();
 66        _commitStateHandler = serviceProvider.GetRequiredService<ICommitStateHandler>();
 67        SubStatus = WorkflowSubStatus.Pending;
 68        Id = id;
 69        CorrelationId = correlationId;
 70        ParentWorkflowInstanceId = parentWorkflowInstanceId;
 71        _activityExecutionContexts = new List<ActivityExecutionContext>();
 72        Scheduler = serviceProvider.GetRequiredService<IActivitySchedulerFactory>().CreateScheduler();
 73        IdentityGenerator = serviceProvider.GetRequiredService<IIdentityGenerator>();
 74        Input = input != null ? new(input, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>(StringComp
 75        Properties = properties != null ? new(properties, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, obj
 76        ExecuteDelegate = executeDelegate;
 77        TriggerActivityId = triggerActivityId;
 78        CreatedAt = createdAt;
 79        UpdatedAt = createdAt;
 80        CancellationToken = cancellationToken;
 81        Incidents = incidents.ToList();
 82        OriginalBookmarks = originalBookmarks.ToList();
 83        WorkflowGraph = workflowGraph;
 84        var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 85        _cancellationTokenSources.Add(linkedCancellationTokenSource);
 86        _cancellationRegistrations.Add(linkedCancellationTokenSource.Token.Register(CancelWorkflow));
 87    }
 88
 89    /// <summary>
 90    /// Creates a new <see cref="WorkflowExecutionContext"/> for the specified workflow.
 91    /// </summary>
 92    public static async Task<WorkflowExecutionContext> CreateAsync(
 93        IServiceProvider serviceProvider,
 94        WorkflowGraph workflowGraph,
 95        string id,
 96        CancellationToken cancellationToken = default)
 97    {
 98        var systemClock = serviceProvider.GetRequiredService<ISystemClock>();
 99
 100        return await CreateAsync(
 101            serviceProvider,
 102            workflowGraph,
 103            id,
 104            new List<ActivityIncident>(),
 105            new List<Bookmark>(),
 106            systemClock.UtcNow,
 107            cancellationToken: cancellationToken
 108        );
 109    }
 110
 111    /// <summary>
 112    /// Creates a new <see cref="WorkflowExecutionContext"/> for the specified workflow.
 113    /// </summary>
 114    public static async Task<WorkflowExecutionContext> CreateAsync(
 115        IServiceProvider serviceProvider,
 116        WorkflowGraph workflowGraph,
 117        string id,
 118        string? correlationId,
 119        string? parentWorkflowInstanceId = null,
 120        IDictionary<string, object>? input = null,
 121        IDictionary<string, object>? properties = null,
 122        ExecuteActivityDelegate? executeDelegate = null,
 123        string? triggerActivityId = null,
 124        CancellationToken cancellationToken = default)
 125    {
 126        var systemClock = serviceProvider.GetRequiredService<ISystemClock>();
 127
 128        return await CreateAsync(
 129            serviceProvider,
 130            workflowGraph,
 131            id,
 132            new List<ActivityIncident>(),
 133            new List<Bookmark>(),
 134            systemClock.UtcNow,
 135            correlationId,
 136            parentWorkflowInstanceId,
 137            input,
 138            properties,
 139            executeDelegate,
 140            triggerActivityId,
 141            cancellationToken
 142        );
 143    }
 144
 145    /// <summary>
 146    /// Creates a new <see cref="WorkflowExecutionContext"/> for the specified workflow.
 147    /// </summary>
 148    public static async Task<WorkflowExecutionContext> CreateAsync(
 149        IServiceProvider serviceProvider,
 150        WorkflowGraph workflowGraph,
 151        WorkflowState workflowState,
 152        string? correlationId = null,
 153        string? parentWorkflowInstanceId = null,
 154        IDictionary<string, object>? input = null,
 155        IDictionary<string, object>? properties = null,
 156        ExecuteActivityDelegate? executeDelegate = null,
 157        string? triggerActivityId = null,
 158        CancellationToken cancellationToken = default)
 159    {
 160        var workflowExecutionContext = await CreateAsync(
 161            serviceProvider,
 162            workflowGraph,
 163            workflowState.Id,
 164            workflowState.Incidents,
 165            workflowState.Bookmarks,
 166            workflowState.CreatedAt,
 167            correlationId,
 168            parentWorkflowInstanceId,
 169            input,
 170            properties,
 171            executeDelegate,
 172            triggerActivityId,
 173            cancellationToken);
 174
 175        var workflowStateExtractor = serviceProvider.GetRequiredService<IWorkflowStateExtractor>();
 176        await workflowStateExtractor.ApplyAsync(workflowExecutionContext, workflowState);
 177
 178        return workflowExecutionContext;
 179    }
 180
 181    /// <summary>
 182    /// Creates a new <see cref="WorkflowExecutionContext"/> for the specified workflow.
 183    /// </summary>
 184    public static async Task<WorkflowExecutionContext> CreateAsync(
 185        IServiceProvider serviceProvider,
 186        WorkflowGraph workflowGraph,
 187        string id,
 188        IEnumerable<ActivityIncident> incidents,
 189        IEnumerable<Bookmark> originalBookmarks,
 190        DateTimeOffset createdAt,
 191        string? correlationId = null,
 192        string? parentWorkflowInstanceId = null,
 193        IDictionary<string, object>? input = null,
 194        IDictionary<string, object>? properties = null,
 195        ExecuteActivityDelegate? executeDelegate = null,
 196        string? triggerActivityId = null,
 197        CancellationToken cancellationToken = default)
 198    {
 199        // Set up a workflow execution context.
 200        var workflowExecutionContext = new WorkflowExecutionContext(
 201            serviceProvider,
 202            workflowGraph,
 203            id,
 204            correlationId,
 205            parentWorkflowInstanceId,
 206            input,
 207            properties,
 208            executeDelegate,
 209            triggerActivityId,
 210            incidents,
 211            originalBookmarks,
 212            createdAt,
 213            cancellationToken)
 214        {
 215            MemoryRegister = workflowGraph.Workflow.CreateRegister()
 216        };
 217
 218        workflowExecutionContext.ExpressionExecutionContext = new(serviceProvider, workflowExecutionContext.MemoryRegist
 219
 220        await workflowExecutionContext.SetWorkflowGraphAsync(workflowGraph);
 221        return workflowExecutionContext;
 222    }
 223
 224    /// <summary>
 225    /// Assigns the specified workflow to this workflow execution context.
 226    /// </summary>
 227    /// <param name="workflowGraph">The workflow graph to assign.</param>
 228    public async Task SetWorkflowGraphAsync(WorkflowGraph workflowGraph)
 229    {
 230        WorkflowGraph = workflowGraph;
 231        var nodes = workflowGraph.Nodes;
 232
 233        // Register activity types.
 234        var activityTypes = nodes.Select(x => x.Activity.GetType()).Distinct().ToList();
 235        await ActivityRegistry.RegisterAsync(activityTypes, CancellationToken);
 236
 237        // Update the activity execution contexts with the actual activity instances.
 238        foreach (var activityExecutionContext in ActivityExecutionContexts)
 239            activityExecutionContext.Activity = workflowGraph.NodeIdLookup[activityExecutionContext.Activity.NodeId].Act
 240    }
 241
 242    /// Gets the <see cref="IServiceProvider"/>.
 243    public IServiceProvider ServiceProvider { get; }
 244
 245    /// Gets the <see cref="IActivityRegistry"/>.
 246    public IActivityRegistry ActivityRegistry { get; }
 247
 248    /// Gets the <see cref="IActivityRegistryLookupService"/>.
 249    public IActivityRegistryLookupService ActivityRegistryLookup { get; }
 250
 251    /// Gets the workflow graph.
 252    public WorkflowGraph WorkflowGraph { get; private set; }
 253
 254    /// The <see cref="Workflow"/> associated with the execution context.
 255    public Workflow Workflow => WorkflowGraph.Workflow;
 256
 257    /// A graph of the workflow structure.
 258    public ActivityNode Graph => WorkflowGraph.Root;
 259
 260    /// The current status of the workflow.
 261    public WorkflowStatus Status => GetMainStatus(SubStatus);
 262
 263    /// The current sub status of the workflow.
 264    public WorkflowSubStatus SubStatus { get; internal set; }
 265
 266    /// <summary>
 267    /// Gets or sets a value indicating whether the workflow instance is actively executing.
 268    /// </summary>
 269    /// <remarks>
 270    /// This flag is set to <c>true</c> immediately before the workflow begins execution
 271    /// and is set to <c>false</c> once the execution is completed.
 272    /// It can be used to determine if a workflow instance was in-progress in case of unexpected
 273    /// application termination, allowing the system to retry execution upon restarting.
 274    /// </remarks>
 275    public bool IsExecuting { get; set; }
 276
 277    /// The root <see cref="MemoryRegister"/> associated with the execution context.
 278    public MemoryRegister MemoryRegister { get; private set; } = null!;
 279
 280    /// A unique ID of the execution context.
 281    public string Id { get; set; }
 282
 283    /// <inheritdoc />
 284    public IActivity Activity => Workflow;
 285
 286    /// An application-specific identifier associated with the execution context.
 287    public string? CorrelationId { get; set; }
 288
 289    /// Gets or sets the name of the workflow instance.
 290    public string? Name { get; set; }
 291
 292    /// The ID of the workflow instance that triggered this instance.
 293    public string? ParentWorkflowInstanceId { get; set; }
 294
 295    /// The date and time the workflow execution context was created.
 296    public DateTimeOffset CreatedAt { get; set; }
 297
 298    /// The date and time the workflow execution context was last updated.
 299    public DateTimeOffset UpdatedAt { get; set; }
 300
 301    /// The date and time the workflow execution context has finished.
 302    public DateTimeOffset? FinishedAt { get; set; }
 303
 304    /// <summary>
 305    /// Gets the exception that occurred during workflow execution, if any.
 306    /// </summary>
 307    internal Exception? Exception { get; set; }
 308
 309    /// Gets the clock used to determine the current time.
 310    public ISystemClock SystemClock { get; }
 311
 312    /// A flattened list of <see cref="ActivityNode"/>s from the <see cref="Graph"/>.
 313    public IReadOnlyCollection<ActivityNode> Nodes => WorkflowGraph.Nodes.ToList();
 314
 315    /// A map between activity IDs and <see cref="ActivityNode"/>s in the workflow graph.
 316    public IDictionary<string, ActivityNode> NodeIdLookup => WorkflowGraph.NodeIdLookup;
 317
 318    /// A map between hashed activity node IDs and <see cref="ActivityNode"/>s in the workflow graph.
 319    public IDictionary<string, ActivityNode> NodeHashLookup => WorkflowGraph.NodeHashLookup;
 320
 321    /// A map between <see cref="IActivity"/>s and <see cref="ActivityNode"/>s in the workflow graph.
 322    public IDictionary<IActivity, ActivityNode> NodeActivityLookup => WorkflowGraph.NodeActivityLookup;
 323
 324    /// The <see cref="IActivityScheduler"/> for the execution context.
 325    public IActivityScheduler Scheduler { get; }
 326
 327    /// Gets the <see cref="IIdentityGenerator"/>.
 328    public IIdentityGenerator IdentityGenerator { get; }
 329
 330    /// Gets the collection of original bookmarks associated with the workflow execution context.
 331    public ICollection<Bookmark> OriginalBookmarks { get; set; }
 332
 333    /// A collection of collected bookmarks during workflow execution.
 334    public ICollection<Bookmark> Bookmarks { get; set; } = new List<Bookmark>();
 335
 336    /// A diff between the original bookmarks and the current bookmarks.
 337    public Diff<Bookmark> BookmarksDiff => Diff.For(OriginalBookmarks, Bookmarks);
 338
 339    /// <summary>
 340    /// A dictionary of inputs provided at the start of the current workflow execution.
 341    /// </summary>
 342    public IDictionary<string, object> Input { get; set; }
 343
 344    /// <summary>
 345    /// A dictionary of outputs provided by the current workflow execution.
 346    /// </summary>
 347    public IDictionary<string, object> Output { get; set; } = new Dictionary<string, object>();
 348
 349    /// <inheritdoc />
 350    public IDictionary<string, object> Properties { get; set; }
 351
 352    /// <summary>
 353    /// A dictionary that can be used by application code and middleware to store information and even services. Values 
 354    /// All data will be gone once workflow execution completes.
 355    /// </summary>
 356    public IDictionary<object, object> TransientProperties { get; set; } = new Dictionary<object, object>();
 357
 358    /// <summary>
 359    /// A collection of incidents that may have occurred during execution.
 360    /// </summary>
 361    public ICollection<ActivityIncident> Incidents { get; set; }
 362
 363    /// <summary>
 364    /// The current <see cref="ExecuteActivityDelegate"/> delegate to invoke when executing the next activity.
 365    /// </summary>
 366    public ExecuteActivityDelegate? ExecuteDelegate { get; set; }
 367
 368    /// <summary>
 369    /// Provides context about the bookmark that was used to resume workflow execution, if any.
 370    /// </summary>
 371    public ResumedBookmarkContext? ResumedBookmarkContext { get; set; }
 372
 373    /// <summary>
 374    /// The ID of the activity associated with the trigger that caused this workflow execution, if any.
 375    /// </summary>
 376    public string? TriggerActivityId { get; set; }
 377
 378    /// <summary>
 379    /// A set of cancellation tokens that can be used to cancel the workflow execution without cancelling system-level o
 380    /// </summary>
 381    public CancellationToken CancellationToken { get; }
 382
 383    /// <summary>
 384    /// A list of <see cref="ActivityCompletionCallbackEntry"/> callbacks that are invoked when the associated child act
 385    /// </summary>
 386    public ICollection<ActivityCompletionCallbackEntry> CompletionCallbacks => new ReadOnlyCollection<ActivityCompletion
 387
 388    /// <summary>
 389    /// A list of <see cref="ActivityExecutionContext"/>s that are currently active.
 390    /// </summary>
 391    public IReadOnlyCollection<ActivityExecutionContext> ActivityExecutionContexts
 392    {
 393        get => _activityExecutionContexts.AsReadOnly();
 394        internal set => _activityExecutionContexts = value.ToList();
 395    }
 396
 397    /// <summary>
 398    /// The last execution log sequence number. This number is incremented every time a new entry is added to the execut
 399    /// </summary>
 400    public long ExecutionLogSequence { get; set; }
 401
 402    /// <summary>
 403    /// A collection of execution log entries. This collection is flushed when the workflow execution context ends.
 404    /// </summary>
 405    public ICollection<WorkflowExecutionLogEntry> ExecutionLog { get; } = new List<WorkflowExecutionLogEntry>();
 406
 407    /// <summary>
 408    /// The expression execution context for the current workflow execution.
 409    /// </summary>
 410    public ExpressionExecutionContext ExpressionExecutionContext { get; private set; } = null!;
 411
 412    /// <inheritdoc />
 413    public IEnumerable<Variable> Variables => Workflow.Variables;
 414
 415    /// <summary>
 416    /// Resolves the specified service type from the service provider.
 417    /// </summary>
 418    public T GetRequiredService<T>() where T : notnull => ServiceProvider.GetRequiredService<T>();
 419
 420    /// <summary>
 421    /// Resolves the specified service type from the service provider.
 422    /// </summary>
 423    public object GetRequiredService(Type serviceType) => ServiceProvider.GetRequiredService(serviceType);
 424
 425    /// <summary>
 426    /// Resolves the specified service type from the service provider, or creates a new instance if the service type was
 427    /// </summary>
 428    public T GetOrCreateService<T>() where T : notnull => ActivatorUtilities.GetServiceOrCreateInstance<T>(ServiceProvid
 429
 430    /// <summary>
 431    /// Resolves the specified service type from the service provider, or creates a new instance if the service type was
 432    /// </summary>
 433    public object GetOrCreateService(Type serviceType) => ActivatorUtilities.GetServiceOrCreateInstance(ServiceProvider,
 434
 435    /// <summary>
 436    /// Resolves the specified service type from the service provider.
 437    /// </summary>
 438    public T? GetService<T>() where T : notnull => ServiceProvider.GetService<T>();
 439
 440    /// <summary>
 441    /// Resolves the specified service type from the service provider.
 442    /// </summary>
 443    public object? GetService(Type serviceType) => ServiceProvider.GetService(serviceType);
 444
 445    /// <summary>
 446    /// Resolves multiple implementations of the specified service type from the service provider.
 447    /// </summary>
 448    public IEnumerable<T> GetServices<T>() where T : notnull => ServiceProvider.GetServices<T>();
 449
 450    /// <summary>
 451    /// Registers a completion callback for the specified activity.
 452    /// </summary>
 453    public void AddCompletionCallback(ActivityExecutionContext owner, ActivityNode child, ActivityCompletionCallback? co
 454    {
 455        var entry = new ActivityCompletionCallbackEntry(owner, child, completionCallback, tag);
 456        _completionCallbackEntries.Add(entry);
 457    }
 458
 459    /// <summary>
 460    /// Unregisters the completion callback for the specified owner and child activity.
 461    /// </summary>
 462    public ActivityCompletionCallbackEntry? PopCompletionCallback(ActivityExecutionContext owner, ActivityNode child)
 463    {
 464        var entry = _completionCallbackEntries.FirstOrDefault(x => x.Owner == owner && x.Child == child);
 465
 466        if (entry == null)
 467            return null;
 468
 469        RemoveCompletionCallback(entry);
 470        return entry;
 471    }
 472
 473    public void RemoveCompletionCallback(ActivityCompletionCallbackEntry entry) => _completionCallbackEntries.Remove(ent
 474
 475    public void RemoveCompletionCallbacks(IEnumerable<ActivityCompletionCallbackEntry> entries)
 476    {
 477        foreach (var entry in entries.ToList())
 478            _completionCallbackEntries.Remove(entry);
 479    }
 480
 481    /// <summary>
 482    /// Clears all activity completion callback entries from the workflow execution context.
 483    /// </summary>
 484    public void ClearCompletionCallbacks()
 485    {
 486        _completionCallbackEntries.Clear();
 487    }
 488
 489    /// <summary>
 490    /// Finds the activity based on the provided <paramref name="handle"/>.
 491    /// </summary>
 492    /// <param name="handle">The handle containing the identification parameters for the activity.</param>
 493    /// <returns>The activity found based on the handle, or null if no activity is found.</returns>
 494    public IActivity? FindActivity(ActivityHandle handle)
 495    {
 496        return handle.ActivityId != null
 497            ? FindActivityById(handle.ActivityId)
 498            : handle.ActivityNodeId != null
 499                ? FindActivityByNodeId(handle.ActivityNodeId)
 500                : handle.ActivityInstanceId != null
 501                    ? FindActivityByInstanceId(handle.ActivityInstanceId)
 502                    : handle.ActivityHash != null
 503                        ? FindActivityByHash(handle.ActivityHash)
 504                        : null;
 505    }
 506
 507    /// <summary>
 508    /// Returns the <see cref="ActivityNode"/> with the specified activity ID from the workflow graph.
 509    /// </summary>
 510    public ActivityNode? FindNodeById(string nodeId) => NodeIdLookup.TryGetValue(nodeId, out var node) ? node : null;
 511
 512    /// <summary>
 513    /// Returns the <see cref="ActivityNode"/> with the specified hash of the activity node ID from the workflow graph.
 514    /// </summary>
 515    /// <param name="hash">The hash of the activity node ID.</param>
 516    /// <returns>The <see cref="ActivityNode"/> with the specified hash of the activity node ID.</returns>
 517    public ActivityNode? FindNodeByHash(string hash) => NodeHashLookup.TryGetValue(hash, out var node) ? node : null;
 518
 519    /// Returns the <see cref="ActivityNode"/> containing the specified activity from the workflow graph.
 520    public ActivityNode? FindNodeByActivity(IActivity activity)
 521    {
 522        return NodeActivityLookup.TryGetValue(activity, out var node) ? node : null;
 523    }
 524
 525    /// Returns the <see cref="ActivityNode"/> associated with the specified activity ID.
 526    public ActivityNode? FindNodeByActivityId(string activityId) => Nodes.FirstOrDefault(x => x.Activity.Id == activityI
 527
 528    /// Returns the <see cref="IActivity"/> with the specified ID from the workflow graph.
 529    public IActivity? FindActivityByNodeId(string nodeId) => FindNodeById(nodeId)?.Activity;
 530
 531    /// Returns the <see cref="IActivity"/> with the specified ID from the workflow graph.
 532    public IActivity? FindActivityById(string activityId) => FindNodeById(NodeIdLookup.SingleOrDefault(n => n.Key.EndsWi
 533
 534    /// Returns the <see cref="IActivity"/> with the specified hash of the activity node ID from the workflow graph.
 535    /// <param name="hash">The hash of the activity node ID.</param>
 536    /// <returns>The <see cref="IActivity"/> with the specified hash of the activity node ID.</returns>
 537    public IActivity? FindActivityByHash(string hash) => FindNodeByHash(hash)?.Activity;
 538
 539    /// Returns the <see cref="ActivityExecutionContext"/> with the specified activity instance ID.
 540    public IActivity? FindActivityByInstanceId(string activityInstanceId) => ActivityExecutionContexts.FirstOrDefault(x 
 541
 542    /// Returns a custom property with the specified key from the <see cref="Properties"/> dictionary.
 543    public T? GetProperty<T>(string key) => Properties.TryGetValue(key, out var value) ? value.ConvertTo<T>() : default;
 544
 545    /// Sets a custom property with the specified key on the <see cref="Properties"/> dictionary.
 546    public void SetProperty<T>(string key, T value) => Properties[key] = value!;
 547
 548    /// Updates a custom property with the specified key on the <see cref="Properties"/> dictionary.
 549    public T UpdateProperty<T>(string key, Func<T?, T> updater)
 550    {
 551        var value = GetProperty<T?>(key);
 552        value = updater(value);
 553        Properties[key] = value!;
 554        return value;
 555    }
 556
 557    /// Returns true if the <see cref="Properties"/> dictionary contains the specified key.
 558    public bool HasProperty(string name) => Properties.ContainsKey(name);
 559
 560    internal bool CanTransitionTo(WorkflowSubStatus targetSubStatus) => ValidateStatusTransition();
 561
 562    internal void TransitionTo(WorkflowSubStatus subStatus)
 563    {
 564        if (!ValidateStatusTransition())
 565            throw new($"Cannot transition from {SubStatus} to {subStatus}");
 566
 567        SubStatus = subStatus;
 568        UpdatedAt = SystemClock.UtcNow;
 569
 570        if (Status == WorkflowStatus.Finished)
 571            FinishedAt = UpdatedAt;
 572
 573        if (Status == WorkflowStatus.Finished || SubStatus == WorkflowSubStatus.Suspended)
 574        {
 575            foreach (var registration in _cancellationRegistrations)
 576                registration.Dispose();
 577        }
 578    }
 579
 580    /// Creates a new <see cref="ActivityExecutionContext"/> for the specified activity.
 581    public async Task<ActivityExecutionContext> CreateActivityExecutionContextAsync(IActivity activity, ActivityInvocati
 582    {
 583        var activityDescriptor = await ActivityRegistryLookup.FindAsync(activity) ?? throw new ActivityNotFoundException
 584        var tag = options?.Tag;
 585        var parentContext = options?.Owner;
 586        var now = SystemClock.UtcNow;
 587        var id = IdentityGenerator.GenerateId();
 588        var activityExecutionContext = new ActivityExecutionContext(id, this, parentContext, activity, activityDescripto
 589        var variablesToDeclare = options?.Variables ?? [];
 590        var variableContainer = new[]
 591        {
 592            activityExecutionContext.ActivityNode
 593        }.Concat(activityExecutionContext.ActivityNode.Ancestors()).FirstOrDefault(x => x.Activity is IVariableContainer
 594        activityExecutionContext.ExpressionExecutionContext.TransientProperties[ExpressionExecutionContextExtensions.Act
 595
 596        if (variableContainer != null)
 597        {
 598            foreach (var variable in variablesToDeclare)
 599            {
 600                // Declare a dynamic variable on the activity execution context.
 601                activityExecutionContext.DynamicVariables.RemoveWhere(x => x.Name == variable.Name);
 602                activityExecutionContext.DynamicVariables.Add(variable);
 603
 604                // Assign the variable to the expression execution context.
 605                activityExecutionContext.ExpressionExecutionContext.CreateVariable(variable.Name, variable.Value);
 606            }
 607        }
 608
 609        var activityInput = options?.Input ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
 610        activityExecutionContext.ActivityInput.Merge(activityInput);
 611
 612        // Populate call stack fields from options
 613        activityExecutionContext.SchedulingActivityExecutionId = options?.SchedulingActivityExecutionId;
 614        activityExecutionContext.SchedulingWorkflowInstanceId = options?.SchedulingWorkflowInstanceId;
 615
 616        // Calculate call stack depth
 617        if (options?.SchedulingActivityExecutionId != null)
 618        {
 619            // First, try to find the scheduling context in the current workflow
 620            var schedulingContext = ActivityExecutionContexts.FirstOrDefault(x => x.Id == options.SchedulingActivityExec
 621            if (schedulingContext != null)
 622            {
 623                // Found in current workflow - use its depth
 624                activityExecutionContext.SchedulingActivityId = schedulingContext.Activity.Id;
 625                activityExecutionContext.CallStackDepth = schedulingContext.CallStackDepth + 1;
 626            }
 627            else if (options.SchedulingCallStackDepth.HasValue)
 628            {
 629                // Not found but caller provided depth (e.g., cross-workflow invocation)
 630                activityExecutionContext.CallStackDepth = options.SchedulingCallStackDepth.Value + 1;
 631            }
 632            // else: scheduling context not found and no depth provided.
 633            // Depth stays at default (0), which may result in incorrect call stack depth tracking
 634            // if the scheduling context should have been present but wasn't found.
 635        }
 636
 637        return activityExecutionContext;
 638    }
 639
 640    /// Returns a register of recorded activity output.
 641    public ActivityOutputRegister GetActivityOutputRegister() => TransientProperties.GetOrAdd(ActivityOutputRegistryKey,
 642
 643    /// Returns the last activity result.
 644    public object? GetLastActivityResult() => TransientProperties.TryGetValue(LastActivityResultKey, out var value) ? va
 645
 646    /// Adds the specified <see cref="ActivityExecutionContext"/> to the workflow execution context.
 647    public void AddActivityExecutionContext(ActivityExecutionContext context) => _activityExecutionContexts.Add(context)
 648
 649    /// Removes the specified <see cref="ActivityExecutionContext"/> from the workflow execution context.
 650    public void RemoveActivityExecutionContext(ActivityExecutionContext context)
 651    {
 652        _activityExecutionContexts.Remove(context);
 653        context.ParentActivityExecutionContext?.Children.Remove(context);
 654    }
 655
 656    /// Removes the specified <see cref="ActivityExecutionContext"/> from the workflow execution context.
 657    /// <param name="predicate">The predicate used to filter the activity execution contexts to remove.</param>
 658    public void RemoveActivityExecutionContexts(Func<ActivityExecutionContext, bool> predicate)
 659    {
 660        var itemsToRemove = _activityExecutionContexts.Where(predicate).ToList();
 661        foreach (var item in itemsToRemove)
 662            RemoveActivityExecutionContext(item);
 663    }
 664
 665    /// <summary>
 666    /// Removes all completed activity execution contexts that have a parent activity execution context.
 667    /// </summary>
 668    public void ClearCompletedActivityExecutionContexts()
 669    {
 670        RemoveActivityExecutionContexts(x => x is { IsCompleted: true, ParentActivityExecutionContext: not null });
 671    }
 672
 673    public IEnumerable<ActivityExecutionContext> GetActiveActivityExecutionContexts()
 674    {
 675        // Filter out completed activity execution contexts, except for the root Workflow activity context, which stores
 676        // This will currently break scripts accessing activity output directly, but there's a workaround for that via v
 677        // We may ultimately restore direct output access, but differently.
 678        return ActivityExecutionContexts.Where(x => !x.IsCompleted || x.ParentActivityExecutionContext == null);
 679    }
 680
 681    /// <summary>
 682    /// Records the output of the specified activity into the current workflow execution context.
 683    /// </summary>
 684    /// <param name="activityExecutionContext">The <see cref="ActivityExecutionContext"/> of the activity.</param>
 685    /// <param name="outputName">The name of the output.</param>
 686    /// <param name="value">The value of the output.</param>
 687    internal void RecordActivityOutput(ActivityExecutionContext activityExecutionContext, string? outputName, object? va
 688    {
 689        var register = GetActivityOutputRegister();
 690        register.Record(activityExecutionContext, outputName, value);
 691
 692        // If the output name is the default output name, record the value as the last activity result.
 693        if (outputName == ActivityOutputRegister.DefaultOutputName)
 694            TransientProperties[LastActivityResultKey] = value!;
 695    }
 696
 697    private WorkflowStatus GetMainStatus(WorkflowSubStatus subStatus) =>
 698        subStatus switch
 699        {
 700            WorkflowSubStatus.Pending => WorkflowStatus.Running,
 701            WorkflowSubStatus.Cancelled => WorkflowStatus.Finished,
 702            WorkflowSubStatus.Executing => WorkflowStatus.Running,
 703            WorkflowSubStatus.Faulted => WorkflowStatus.Finished,
 704            WorkflowSubStatus.Finished => WorkflowStatus.Finished,
 705            WorkflowSubStatus.Suspended => WorkflowStatus.Running,
 706            _ => throw new ArgumentOutOfRangeException(nameof(subStatus), subStatus, null)
 707        };
 708
 709    // TODO: Check if we should not use the target subStatus here instead.
 710    private bool ValidateStatusTransition()
 711    {
 712        var currentMainStatus = GetMainStatus(SubStatus);
 713        return currentMainStatus != WorkflowStatus.Finished;
 714    }
 715
 716    public Task CommitAsync()
 717    {
 718        return _commitStateHandler.CommitAsync(this, CancellationToken);
 719    }
 720}

Methods/Properties

get_Owner()