Refactor activity execution state tracking
Introduce `IsExecuting` flag to explicitly track activity execution state, improving clarity and control over workflow activity handling. Adjust scheduling intervals and add concurrency handling for database updates to enhance reliability and performance in interrupted workflows.
This commit is contained in:
parent
3c0ab8b172
commit
3beedb9ec0
|
|
@ -681,10 +681,10 @@ services.Configure<RecurringTaskOptions>(options =>
|
|||
options.Schedule.ConfigureTask<TriggerBookmarkQueueRecurringTask>(TimeSpan.FromSeconds(300));
|
||||
options.Schedule.ConfigureTask<PurgeBookmarkQueueRecurringTask>(TimeSpan.FromSeconds(300));
|
||||
options.Schedule.ConfigureTask<UpdateExpiredSecretsRecurringTask>(TimeSpan.FromHours(4));
|
||||
options.Schedule.ConfigureTask<RestartInterruptedWorkflowsTask>(TimeSpan.FromMinutes(1));
|
||||
options.Schedule.ConfigureTask<RestartInterruptedWorkflowsTask>(TimeSpan.FromSeconds(15));
|
||||
});
|
||||
|
||||
services.Configure<RuntimeOptions>(options => { options.InactivityThreshold = TimeSpan.FromMinutes(1); });
|
||||
services.Configure<RuntimeOptions>(options => { options.InactivityThreshold = TimeSpan.FromSeconds(15); });
|
||||
services.Configure<BookmarkQueuePurgeOptions>(options => options.Ttl = TimeSpan.FromSeconds(10));
|
||||
services.Configure<CachingOptions>(options => options.CacheDuration = TimeSpan.FromDays(1));
|
||||
|
||||
|
|
|
|||
|
|
@ -157,7 +157,35 @@ public class EFCoreWorkflowInstanceStore : IWorkflowInstanceStore
|
|||
Id = workflowInstanceId,
|
||||
UpdatedAt = value
|
||||
};
|
||||
await _store.UpdatePartialAsync(entity, [x => x.UpdatedAt], cancellationToken);
|
||||
|
||||
await using var dbContext = await _store.CreateDbContextAsync(cancellationToken);
|
||||
dbContext.Attach(entity);
|
||||
dbContext.Entry(entity).Property(x => x.UpdatedAt).IsModified = true;
|
||||
|
||||
try
|
||||
{
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
catch (DbUpdateConcurrencyException e)
|
||||
{
|
||||
foreach (var entry in e.Entries)
|
||||
{
|
||||
var proposedValues = entry.CurrentValues;
|
||||
var databaseValues = await entry.GetDatabaseValuesAsync(cancellationToken);
|
||||
|
||||
if(databaseValues == null)
|
||||
continue;
|
||||
|
||||
var updatedAtProperty = entry.Metadata.GetProperty(nameof(WorkflowInstance.UpdatedAt));
|
||||
var proposedValue = (DateTimeOffset)proposedValues[updatedAtProperty]!;
|
||||
var databaseValue = (DateTimeOffset)databaseValues[updatedAtProperty]!;
|
||||
|
||||
if (proposedValue > databaseValue)
|
||||
proposedValues[updatedAtProperty] = proposedValue;
|
||||
|
||||
entry.OriginalValues.SetValues(databaseValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable
|
|||
var expressionExecutionContextProps = ExpressionExecutionContextExtensions.CreateActivityExecutionContextPropertiesFrom(workflowExecutionContext, workflowExecutionContext.Input);
|
||||
expressionExecutionContextProps[ExpressionExecutionContextExtensions.ActivityKey] = activity;
|
||||
ExpressionExecutionContext = new(workflowExecutionContext.ServiceProvider, new(), parentActivityExecutionContext?.ExpressionExecutionContext ?? workflowExecutionContext.ExpressionExecutionContext, expressionExecutionContextProps, Taint, CancellationToken);
|
||||
;
|
||||
Activity = activity;
|
||||
ActivityDescriptor = activityDescriptor;
|
||||
StartedAt = startedAt;
|
||||
|
|
@ -84,6 +83,17 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable
|
|||
/// </summary>
|
||||
public bool IsCompleted => Status is ActivityStatus.Completed or ActivityStatus.Canceled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the activity is actively executing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This flag is set to <c>true</c> immediately before the activity begins execution
|
||||
/// and is set to <c>false</c> once the execution is completed.
|
||||
/// It can be used to determine if an activity was in-progress in case of unexpected
|
||||
/// application termination, allowing the system to retry execution upon restarting.
|
||||
/// </remarks>
|
||||
public bool IsExecuting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The workflow execution context.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public enum ActivityStatus
|
|||
Pending,
|
||||
|
||||
/// <summary>
|
||||
/// The activity is in the Running state. Note that event if an activity is running, it may not be executing.
|
||||
/// The activity is in the Running state. While in this state, the activity is not necessarily being actively executed. This state represents a logical status rather than a physical action.
|
||||
/// </summary>
|
||||
Running,
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, I
|
|||
context.AddExecutionLogEntry("Precondition Failed", "Cannot execute at this time");
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark activity as executing.
|
||||
context.IsExecuting = true;
|
||||
|
||||
// Conditionally commit the workflow state.
|
||||
if (ShouldCommit(context, ActivityLifetimeEvent.ActivityExecuting))
|
||||
|
|
@ -83,6 +86,9 @@ public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, I
|
|||
workflowExecutionContext.Bookmarks.AddRange(context.Bookmarks);
|
||||
logger.LogDebug("Added {BookmarkCount} bookmarks to the workflow execution context", context.Bookmarks.Count);
|
||||
}
|
||||
|
||||
// Mark activity as executed.
|
||||
context.IsExecuting = false;
|
||||
|
||||
// Conditionally commit the workflow state.
|
||||
if (ShouldCommit(context, ActivityLifetimeEvent.ActivityExecuted))
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ public class WorkflowRunner(
|
|||
else
|
||||
{
|
||||
// Check if there are any leaf nodes in the Pending state.
|
||||
var pendingActivityExecutionContexts = workflowExecutionContext.ActivityExecutionContexts.Where(x => x.Status == ActivityStatus.Pending).ToList();
|
||||
var pendingActivityExecutionContexts = workflowExecutionContext.ActivityExecutionContexts.Where(x => x.IsExecuting).ToList();
|
||||
|
||||
if( pendingActivityExecutionContexts.Count > 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
|
|||
activityExecutionContext.ActivityState.Merge(activityExecutionContextState.ActivityState);
|
||||
|
||||
activityExecutionContext.TransitionTo(activityExecutionContextState.Status);
|
||||
activityExecutionContext.IsExecuting = activityExecutionContextState.IsExecuting;
|
||||
activityExecutionContext.StartedAt = activityExecutionContextState.StartedAt;
|
||||
activityExecutionContext.CompletedAt = activityExecutionContextState.CompletedAt;
|
||||
activityExecutionContext.Tag = activityExecutionContextState.Tag;
|
||||
|
|
@ -232,6 +233,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
|
|||
Properties = activityExecutionContext.Properties,
|
||||
ActivityState = activityExecutionContext.ActivityState,
|
||||
Status = activityExecutionContext.Status,
|
||||
IsExecuting = activityExecutionContext.IsExecuting,
|
||||
StartedAt = activityExecutionContext.StartedAt,
|
||||
CompletedAt = activityExecutionContext.CompletedAt,
|
||||
Tag = activityExecutionContext.Tag,
|
||||
|
|
|
|||
|
|
@ -55,6 +55,11 @@ public class ActivityExecutionContextState
|
|||
/// The status of the activity.
|
||||
/// </summary>
|
||||
public ActivityStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the activity is actively executing.
|
||||
/// </summary>
|
||||
public bool IsExecuting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the activity execution began.
|
||||
|
|
|
|||
Loading…
Reference in a new issue