Merge pull request #6318 from elsa-workflows/perf/runtime

Optimize performance
This commit is contained in:
Sipke Schoorstra 2025-01-20 18:46:24 +01:00 committed by GitHub
commit 7edbdb9bc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 54 additions and 70 deletions

View file

@ -5,6 +5,7 @@ on:
branches:
- 'main'
- 'bug/*'
- 'perf/*'
release:
types: [ prereleased, published ]
env:

View file

@ -31,9 +31,7 @@ public class Flowchart : Container
/// <summary>
/// The activity to execute when the flowchart starts.
/// </summary>
[Port]
[Browsable(false)]
public IActivity? Start { get; set; }
[Port] [Browsable(false)] public IActivity? Start { get; set; }
/// <summary>
/// A list of connections between activities.
@ -98,8 +96,8 @@ public class Flowchart : Container
{
var workflowExecutionContext = context.WorkflowExecutionContext;
var activityIds = Activities.Select(x => x.Id).ToList();
var descendantContexts = context.GetDescendents().Where(x => x.ParentActivityExecutionContext == context).ToList();
var activityExecutionContexts = descendantContexts.Where(x => activityIds.Contains(x.Activity.Id)).ToList();
var children = context.Children;
var hasRunningActivityInstances = children.Where(x => activityIds.Contains(x.Activity.Id)).Any(x => x.Status == ActivityStatus.Running);
var hasPendingWork = workflowExecutionContext.Scheduler.List().Any(workItem =>
{
@ -117,8 +115,6 @@ public class Flowchart : Container
return ancestors.Any(x => x == context);
});
var hasRunningActivityInstances = activityExecutionContexts.Any(x => x.Status == ActivityStatus.Running);
return hasRunningActivityInstances || hasPendingWork;
}
@ -186,7 +182,7 @@ public class Flowchart : Container
var executionCount = scope.GetExecutionCount(activity);
var haveInboundActivitiesExecuted = inboundActivities.All(x => scope.GetExecutionCount(x) > executionCount);
if (haveInboundActivitiesExecuted)
if (haveInboundActivitiesExecuted)
await flowchartContext.ScheduleActivityAsync(activity, OnChildCompletedAsync);
}
else
@ -231,7 +227,7 @@ public class Flowchart : Container
if (!hasPendingWork)
{
var hasFaultedActivities = context.GetActiveChildren().Any(x => x.Status == ActivityStatus.Faulted);
var hasFaultedActivities = context.Children.Any(x => x.Status == ActivityStatus.Faulted);
if (!hasFaultedActivities)
{

View file

@ -9,7 +9,7 @@ public partial class ActivityExecutionContext
/// <summary>
/// Complete the current activity. This should only be called by activities that explicitly suppress automatic-completion.
/// </summary>
public async ValueTask CompleteActivityAsync(object? result = default)
public async ValueTask CompleteActivityAsync(object? result = null)
{
var outcomes = result as Outcomes;
@ -28,8 +28,8 @@ public partial class ActivityExecutionContext
return;
// Cancel any non-completed child activities.
var childContexts = WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == this && x.CanCancelActivity()).ToList();
var childContexts = Children.Where(x => x.CanCancelActivity()).ToList();
foreach (var childContext in childContexts)
await childContext.CancelActivityAsync();

View file

@ -13,7 +13,7 @@ public partial class ActivityExecutionContext
/// <param name="source">The source of the activity. For example, the source file name and line number in case of composite activities.</param>
/// <param name="payload">Any contextual data related to this event.</param>
/// <returns>Returns the created <see cref="WorkflowExecutionLogEntry"/>.</returns>
public WorkflowExecutionLogEntry AddExecutionLogEntry(string eventName, string? message = default, string? source = default, object? payload = default)
public WorkflowExecutionLogEntry AddExecutionLogEntry(string eventName, string? message = null, string? source = null, object? payload = null)
{
var logEntry = new WorkflowExecutionLogEntry(
Id,

View file

@ -21,6 +21,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable
private readonly ISystemClock _systemClock;
private readonly List<Bookmark> _bookmarks = [];
private long _executionCount;
private ActivityExecutionContext? _parentActivityExecutionContext;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityExecutionContext"/> class.
@ -39,7 +40,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable
{
_systemClock = systemClock;
WorkflowExecutionContext = workflowExecutionContext;
ParentActivityExecutionContext = parentActivityExecutionContext;
_parentActivityExecutionContext = parentActivityExecutionContext;
ExpressionExecutionContext = expressionExecutionContext;
Activity = activity;
ActivityDescriptor = activityDescriptor;
@ -84,7 +85,15 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable
/// <summary>
/// The parent activity execution context, if any.
/// </summary>
public ActivityExecutionContext? ParentActivityExecutionContext { get; internal set; }
public ActivityExecutionContext? ParentActivityExecutionContext
{
get => _parentActivityExecutionContext;
internal set
{
_parentActivityExecutionContext = value;
_parentActivityExecutionContext?.Children.Add(this);
}
}
/// <summary>
/// The expression execution context.
@ -159,6 +168,8 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable
/// </summary>
/// <remarks>As of tool version 3.0, all activity Ids are already unique, so there's no need to construct a hierarchical ID</remarks>
public string NodeId => ActivityNode.NodeId;
public ISet<ActivityExecutionContext> Children { get; } = new HashSet<ActivityExecutionContext>();
/// <summary>
/// A list of bookmarks created by the current activity.

View file

@ -346,7 +346,7 @@ public partial class WorkflowExecutionContext : IExecutionContext
/// </summary>
public IReadOnlyCollection<ActivityExecutionContext> ActivityExecutionContexts
{
get => _activityExecutionContexts.ToList();
get => _activityExecutionContexts.AsReadOnly();
internal set => _activityExecutionContexts = value.ToList();
}

View file

@ -197,50 +197,6 @@ public static partial class ActivityExecutionContextExtensions
}
}
/// <summary>
/// Returns a flattened list of the current context's descendants.
/// </summary>
public static IEnumerable<ActivityExecutionContext> GetDescendents(this ActivityExecutionContext context)
{
var children = context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context).ToList();
foreach (var child in children)
{
yield return child;
foreach (var descendent in GetDescendents(child))
yield return descendent;
}
}
/// <summary>
/// Returns a flattened list of the current context's immediate active children.
/// </summary>
public static IEnumerable<ActivityExecutionContext> GetActiveChildren(this ActivityExecutionContext context) =>
context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context);
/// <summary>
/// Returns a flattened list of the current context's immediate children.
/// </summary>
public static IEnumerable<ActivityExecutionContext> GetChildren(this ActivityExecutionContext context) =>
context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context);
/// <summary>
/// Returns a flattened list of the current context's descendants.
/// </summary>
public static IEnumerable<ActivityExecutionContext> GetDescendants(this ActivityExecutionContext context)
{
var children = context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context).ToList();
foreach (var child in children)
{
yield return child;
foreach (var descendant in child.GetDescendants())
yield return descendant;
}
}
/// <summary>
/// Send a signal up the current hierarchy of ancestors.
/// </summary>

View file

@ -5,6 +5,10 @@ namespace Elsa.Workflows.Models;
/// </summary>
public class ActivityNode
{
private readonly List<ActivityNode> _parents = new();
private readonly List<ActivityNode> _children = new();
private string? _nodeId;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityNode"/> class.
/// </summary>
@ -23,8 +27,13 @@ public class ActivityNode
{
get
{
var ancestorIds = Ancestors().Reverse().Select(x => x.Activity.Id).ToList();
return ancestorIds.Any() ? $"{string.Join(":", ancestorIds)}:{Activity.Id}" : Activity.Id;
if (_nodeId == null)
{
var ancestorIds = Ancestors().Reverse().Select(x => x.Activity.Id).ToList();
_nodeId = ancestorIds.Any() ? $"{string.Join(":", ancestorIds)}:{Activity.Id}" : Activity.Id;
}
return _nodeId;
}
}
@ -41,12 +50,23 @@ public class ActivityNode
/// <summary>
/// Gets the parents of this node.
/// </summary>
public ICollection<ActivityNode> Parents { get; set; } = new List<ActivityNode>();
public IReadOnlyCollection<ActivityNode> Parents => _parents.AsReadOnly();
/// <summary>
/// Gets the children of this node.
/// </summary>
public ICollection<ActivityNode> Children { get; set; } = new List<ActivityNode>();
public ICollection<ActivityNode> Children => _children.AsReadOnly();
public void AddParent(ActivityNode parent)
{
_parents.Add(parent);
_nodeId = null;
}
public void AddChild(ActivityNode child)
{
_children.Add(child);
}
/// <summary>
/// Gets the descendants of this node.
@ -85,7 +105,7 @@ public class ActivityNode
/// Gets the siblings of this node.
/// </summary>
public IEnumerable<ActivityNode> Siblings() => Parents.SelectMany(parent => parent.Children);
/// <summary>
/// Gets the siblings and cousins of this node.
/// </summary>

View file

@ -71,12 +71,12 @@ public class ActivityVisitor : IActivityVisitor
if (childNode == null)
{
childNode = new ActivityNode(activity, activityPort.PortName);
childNode = new(activity, activityPort.PortName);
collectedNodes.Add(childNode);
}
childNode.Parents.Add(pair.Node);
pair.Node.Children.Add(childNode);
childNode.AddParent(pair.Node);
pair.Node.AddChild(childNode);
collectedActivities.Add(activity);
await VisitRecursiveAsync((childNode, activity), visitorContext, cancellationToken);
}

View file

@ -21,7 +21,7 @@ public class QueueBasedActivityScheduler : IActivityScheduler
public ActivityWorkItem Take() => _queue.Dequeue();
/// <inheritdoc />
public IEnumerable<ActivityWorkItem> List() => _queue.ToList();
public IEnumerable<ActivityWorkItem> List() => _queue;
/// <inheritdoc />
public bool Any(Func<ActivityWorkItem, bool> predicate) => _queue.Any(predicate);