Adds activity execution call stack support#7204
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request implements a comprehensive call stack tracking mechanism for Elsa workflows, enabling visibility into the complete invocation hierarchy from root workflow through all parent activities to a specific activity execution. The implementation adds new fields to track scheduling relationships, updates all composite activities to propagate this information, includes database migrations for 5 providers (SQLite, SQL Server, PostgreSQL, Oracle, MySQL), and provides REST API endpoints for querying call stacks with pagination support.
Changes:
- Adds call stack tracking fields (
SchedulingActivityExecutionId,SchedulingActivityId,SchedulingWorkflowInstanceId,CallStackDepth) to runtime contexts and persisted entities - Updates scheduling infrastructure to thread call stack information through the execution pipeline
- Modifies composite activities (Sequence, Parallel, While, Flowchart) to explicitly track temporal execution predecessors
- Implements cross-workflow call stack linkage in ExecuteWorkflow and DispatchWorkflow activities
- Adds database migrations for all EF Core providers with new columns
- Creates REST API endpoint for retrieving paginated call stacks
- Provides extension methods for reconstructing execution chains from both runtime contexts and persisted records
Reviewed changes
Copilot reviewed 56 out of 61 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| ActivityExecutionContext.cs | Adds scheduling context fields to track temporal execution predecessors |
| ActivityExecutionRecord.cs | Adds persisted call stack fields including depth calculation |
| ScheduleWorkOptions.cs, ActivityWorkItem.cs, etc. | Threads scheduling IDs through all scheduling models |
| DefaultActivityExecutionMapper.cs | Calculates call stack depth by traversing scheduling chain |
| Sequence.cs, While.cs, Parallel.cs, Flowchart.*.cs | Updates composite activities to propagate scheduling context |
| ExecuteWorkflow.cs, DispatchWorkflow.cs | Implements cross-workflow call stack linkage |
| MemoryActivityExecutionStore.cs, EFCoreActivityExecutionLogStore.cs | Implements GetExecutionChainAsync for reconstructing call stacks |
| V3_7 migrations (all providers) | Adds database columns for call stack tracking |
| GetCallStack/Endpoint.cs | REST API endpoint for querying call stacks with pagination |
| IActivityExecutionsApi.cs | API client interface additions |
| WorkflowExecutionContext.cs | Adds ambient scheduling context properties (unused) |
Files not reviewed (5)
- src/modules/Elsa.Persistence.EFCore.MySql/Migrations/Runtime/20260122123013_V3_7.Designer.cs: Language not supported
- src/modules/Elsa.Persistence.EFCore.Oracle/Migrations/Runtime/20260122123056_V3_7.Designer.cs: Language not supported
- src/modules/Elsa.Persistence.EFCore.PostgreSql/Migrations/Runtime/20260122123049_V3_7.Designer.cs: Language not supported
- src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Runtime/20260122123023_V3_7.Designer.cs: Language not supported
- src/modules/Elsa.Persistence.EFCore.Sqlite/Migrations/Runtime/20260122123040_V3_7.Designer.cs: Language not supported
|
@sfmskywalker I've opened a new pull request, #7244, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@sfmskywalker I've opened a new pull request, #7245, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@sfmskywalker I've opened a new pull request, #7250, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 99 out of 104 changed files in this pull request and generated 6 comments.
Files not reviewed (5)
- src/modules/Elsa.Persistence.EFCore.MySql/Migrations/Runtime/20260122123013_V3_7.Designer.cs: Language not supported
- src/modules/Elsa.Persistence.EFCore.Oracle/Migrations/Runtime/20260122123056_V3_7.Designer.cs: Language not supported
- src/modules/Elsa.Persistence.EFCore.PostgreSql/Migrations/Runtime/20260122123049_V3_7.Designer.cs: Language not supported
- src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Runtime/20260122123023_V3_7.Designer.cs: Language not supported
- src/modules/Elsa.Persistence.EFCore.Sqlite/Migrations/Runtime/20260122123040_V3_7.Designer.cs: Language not supported
| public static Task<Page<ActivityExecutionRecord>> GetExecutionChainAsync( | ||
| this ActivityExecutionRecord record, | ||
| IActivityExecutionStore store, | ||
| bool includeCrossWorkflowChain = true, |
There was a problem hiding this comment.
This file references IActivityExecutionStore but doesn’t import the Elsa.Workflows.Runtime namespace (and the type isn’t in Elsa.Workflows.Runtime.Extensions). As written, this won’t compile unless you fully qualify the type or add the appropriate using.
| var result = await workflowDefinitionService.TryFindWorkflowGraphAsync(definitionHandle, cancellationToken); | ||
| if (!result.WorkflowDefinitionExists) | ||
| { | ||
| logger.LogWarning("Workflow definition not found: {WorkflowDefinitionId}", definitionHandle.DefinitionId); | ||
| return null; |
There was a problem hiding this comment.
TryGetWorkflowGraphAsync logs definitionHandle.DefinitionId, but handles created via WorkflowDefinitionHandle.ByDefinitionVersionId(...) have DefinitionId == null. This will produce unhelpful warnings. Log definitionHandle.DefinitionVersionId (or definitionHandle.ToString()) so the warning contains an identifier.
| // Denormalize the scheduling activity ID for convenience | ||
| if (options?.SchedulingActivityExecutionId != null) | ||
| { | ||
| var schedulingContext = ActivityExecutionContexts.FirstOrDefault(x => x.Id == options.SchedulingActivityExecutionId); | ||
| if (schedulingContext != null) | ||
| { | ||
| activityExecutionContext.SchedulingActivityId = schedulingContext.Activity.Id; | ||
| activityExecutionContext.CallStackDepth = schedulingContext.CallStackDepth + 1; | ||
| } | ||
| } |
There was a problem hiding this comment.
When SchedulingActivityExecutionId refers to an execution in a different workflow instance (e.g., ExecuteWorkflow/DispatchWorkflow), it won’t be present in ActivityExecutionContexts, so SchedulingActivityId and CallStackDepth are left at their defaults (depth stays 0). That makes persisted CallStackDepth incorrect for cross-workflow call stacks and inconsistent with the doc comment that depth is derived from the scheduling chain. Consider passing the parent depth explicitly (e.g., via options) or making CallStackDepth nullable/setting a sentinel when the predecessor context is not available locally.
| var currentId = activityExecutionId; | ||
|
|
||
| while (currentId != null && visited.Add(currentId)) |
There was a problem hiding this comment.
currentId is inferred as non-nullable string, but later gets assigned from record.SchedulingActivityExecutionId (nullable). With nullable reference types enabled this won’t compile. Declare currentId as string? (and keep the null-check in the loop) or otherwise coalesce/guard before assignment.
| if (currentContext.SchedulingActivityExecutionId != null) | ||
| { | ||
| currentContext = currentContext.WorkflowExecutionContext.ActivityExecutionContexts | ||
| .FirstOrDefault(x => x.Id == currentContext.SchedulingActivityExecutionId); | ||
| } |
There was a problem hiding this comment.
currentContext is inferred as non-nullable ActivityExecutionContext, but FirstOrDefault(...) returns a nullable value. This causes a nullability conversion error (and the while (currentContext != null ...) check becomes meaningless). Declare currentContext as ActivityExecutionContext? so the loop/assignment type-checks correctly.
| { | ||
| // Task was cancelled, likely due to shutdown - this is expected, don't log as error | ||
| } | ||
| catch (Exception e) when (_disposed || _cancellationRequested || cancellationToken.IsCancellationRequested) |
There was a problem hiding this comment.
Condition is always false because of access to field _disposed.
Condition is always false because of access to field _disposed.
| catch (Exception e) when (_disposed || _cancellationRequested || cancellationToken.IsCancellationRequested) | |
| catch (Exception e) when (_cancellationRequested || cancellationToken.IsCancellationRequested) |
Implements a call stack mechanism to track the execution chain, enabling visibility into the invocation hierarchy. Introduces new fields to scheduling models and runtime contexts to store call stack information. Includes EF Core migrations for various database providers to support new columns in `ActivityExecutionRecords`. Provides an API to query and reconstruct the call stack for a given activity execution.
… chain pagination
Simplifies scheduling logic by removing ambient scope mechanisms, refactoring scheduling context handling, and updating affected classes accordingly.
…vity execution call stack
…ecution tracking Introduce fields for aggregated fault count, scheduling context, workflow instance details, and call stack depth to enhance execution monitoring and debugging capabilities.
Introduced components and models to display a call stack for activity executions in the Workflow Instance Viewer. This includes UI elements for call stack rendering, error handling, and data integration with activity execution records.
Added cycle detection using a `HashSet` to prevent infinite loops when traversing activity execution chains in multiple storage implementations. Updated unit tests to validate correct handling of circular references and chain traversal.
Centralized the `GetExecutionChainAsync` method into an extension class to streamline and unify its implementation across stores. Removed redundant implementations from individual stores and updated interfaces to utilize the new extension method. This reduces code duplication and simplifies future maintenance.
Integrated the `CallStackDepth` property into `ActivityExecutionContext`, `ActivityExecutionContextState`, and related classes to track and manage the call stack depth of activity executions. Removed obsolete depth calculation logic to streamline the process.
- Add `WorkflowExecutionContextTests` to verify correct calculation of call stack depth during activity execution. - Add `WorkflowStateExtractorTests` to ensure call stack depth is preserved during state extraction and application.
…tCallStack/Endpoint.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…tCallStack/Endpoint.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…n logic - Implement `ActivityExecutionRecordFilter` for precise query matching by ID. - Add await logic for task completion in command handler middleware.
* Initial plan * Add missing indexes for call stack columns in all provider migrations Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> * Optimize migrations by creating columns with correct indexable types Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>
- Introduce tests for `GetExecutionChainAsync` covering scenarios of empty results, single records, multi-level chain traversal, workflow boundary constraints, pagination, and circular references.
- Replace mock setup with `CreateStore` helper for clean and clear test arrangements. - Remove unused imports and clean up test setup for improved readability and maintenance.
212c69c to
7b9a27e
Compare
| ExistingActivityExecutionContext = scheduledActivity.Options.ExistingActivityInstanceId != null ? context.WorkflowExecutionContext.ActivityExecutionContexts.FirstOrDefault(x => x.Id == scheduledActivity.Options.ExistingActivityInstanceId) : null, | ||
| Variables = scheduledActivity.Options?.Variables, | ||
| CompletionCallback = !string.IsNullOrEmpty(scheduledActivity.Options?.CompletionCallback) && owner != null ? owner.Activity.GetActivityCompletionCallback(scheduledActivity.Options.CompletionCallback) : null, | ||
| CompletionCallback = !string.IsNullOrEmpty(scheduledActivity.Options?.CompletionCallback) && owner != null ? owner.Activity.GetActivityCompletionCallback(scheduledActivity.Options.CompletionCallback) : default, |
|
Closing in favor of #7271 |
Implements a call stack mechanism to track the execution chain, enabling visibility into the invocation hierarchy.
Introduces new fields to scheduling models and runtime contexts to store call stack information.
Includes EF Core migrations for various database providers to support new columns in
ActivityExecutionRecords.Provides an API to query and reconstruct the call stack for a given activity execution.