Start wait-forever activity (w/ default try-cancel cancellation) and sleep
Start quickly-completing local activity and send cancel for the above activity (in the same activation), then wait first of local activity and activity
The activation on non-replay after that last part will be:
Resolve activity cancelled
Resolve local activity completed
But during replay it is:
Resolve local activity completed
Resolve activity cancelled
Any pieces that rely on this ordering therefore will have a non-determinism error. Here is a workflow in .NET that demonstrates the issue:
[Workflow]
public class AttemptWorkflow
{
[Activity]
public static Task WaitAsync(int waitMs) =>
Task.Delay(waitMs, ActivityExecutionContext.Current.CancellationToken);
[WorkflowRun]
public async Task RunAsync()
{
// Start a long activity that does not wait for cancel
using var cts = CancellationTokenSource.CreateLinkedTokenSource(Workflow.CancellationToken);
var activityTask = Workflow.RunTaskAsync(() => Workflow.ExecuteActivityAsync(
() => WaitAsync(50000),
new()
{
StartToCloseTimeout = TimeSpan.FromHours(1),
CancellationToken = cts.Token,
}));
// Roll over the task
await Workflow.DelayAsync(1);
// Run local activity and cancel the activity
var localActivityTask = Workflow.RunTaskAsync(() => Workflow.ExecuteLocalActivityAsync(
() => WaitAsync(459),
new() { StartToCloseTimeout = TimeSpan.FromHours(1) }));
await Workflow.RunTaskAsync(async () => cts.Cancel());
// Wait on both
await Workflow.WhenAnyAsync(activityTask, localActivityTask);
// Only if local activity status is done will we start a timer
Console.WriteLine("ACT DONE: {0}, LOCAL ACT DONE: {1}", activityTask.Status, localActivityTask.Status);
if (localActivityTask.Status == TaskStatus.RanToCompletion)
{
await Workflow.DelayAsync(1);
}
}
}
Describe the bug
Given a workflow that does the following:
The activation on non-replay after that last part will be:
But during replay it is:
Any pieces that rely on this ordering therefore will have a non-determinism error. Here is a workflow in .NET that demonstrates the issue:
Relevant portion of non-replay log:
Relevant portion of replay log: