Skip to content

Adds activity execution call stack support#7204

Closed
sfmskywalker wants to merge 20 commits into
mainfrom
feat/activity-call-stack
Closed

Adds activity execution call stack support#7204
sfmskywalker wants to merge 20 commits into
mainfrom
feat/activity-call-stack

Conversation

@sfmskywalker

Copy link
Copy Markdown
Member

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.

@sfmskywalker sfmskywalker marked this pull request as ready for review February 4, 2026 18:59
Copilot AI review requested due to automatic review settings February 4, 2026 18:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/modules/Elsa.Workflows.Runtime/Stores/MemoryActivityExecutionStore.cs Outdated
Comment thread src/modules/Elsa.Persistence.EFCore/Modules/Runtime/ActivityExecutionLogStore.cs Outdated
Comment thread src/modules/Elsa.Workflows.Runtime/Services/DefaultActivityExecutionMapper.cs Outdated
Comment thread src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs Outdated

Copilot AI commented Feb 4, 2026

Copy link
Copy Markdown
Contributor

@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.

Copilot AI commented Feb 4, 2026

Copy link
Copy Markdown
Contributor

@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.

Copilot AI commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

@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.

Comment thread src/modules/Elsa.Common/RecurringTasks/ScheduledTimer.cs Fixed
Comment thread src/modules/Elsa.Common/RecurringTasks/ScheduledTimer.cs Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +22 to +25
public static Task<Page<ActivityExecutionRecord>> GetExecutionChainAsync(
this ActivityExecutionRecord record,
IActivityExecutionStore store,
bool includeCrossWorkflowChain = true,

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +233 to +237
var result = await workflowDefinitionService.TryFindWorkflowGraphAsync(definitionHandle, cancellationToken);
if (!result.WorkflowDefinitionExists)
{
logger.LogWarning("Workflow definition not found: {WorkflowDefinitionId}", definitionHandle.DefinitionId);
return null;

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +611 to +620
// 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;
}
}

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +28
var currentId = activityExecutionId;

while (currentId != null && visited.Add(currentId))

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +505 to +509
if (currentContext.SchedulingActivityExecutionId != null)
{
currentContext = currentContext.WorkflowExecutionContext.ActivityExecutionContexts
.FirstOrDefault(x => x.Id == currentContext.SchedulingActivityExecutionId);
}

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
{
// Task was cancelled, likely due to shutdown - this is expected, don't log as error
}
catch (Exception e) when (_disposed || _cancellationRequested || cancellationToken.IsCancellationRequested)

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Condition is always false because of access to field _disposed.
Condition is always false because of access to field _disposed.

Suggested change
catch (Exception e) when (_disposed || _cancellationRequested || cancellationToken.IsCancellationRequested)
catch (Exception e) when (_cancellationRequested || cancellationToken.IsCancellationRequested)

Copilot uses AI. Check for mistakes.
sfmskywalker and others added 15 commits February 7, 2026 11:07
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.
Simplifies scheduling logic by removing ambient scope mechanisms, refactoring scheduling context handling, and updating affected classes accordingly.
…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.
@sfmskywalker sfmskywalker force-pushed the feat/activity-call-stack branch from 212c69c to 7b9a27e Compare February 7, 2026 10:47
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,
@sfmskywalker

Copy link
Copy Markdown
Member Author

Closing in favor of #7271

@sfmskywalker sfmskywalker deleted the feat/activity-call-stack branch April 11, 2026 09:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants