From d505adcf9620129924cdb38e3569fa7a8aff6680 Mon Sep 17 00:00:00 2001 From: Chad Retz Date: Tue, 8 Oct 2024 11:28:44 -0500 Subject: [PATCH] Detached cancellation test (#352) --- .github/workflows/ci.yml | 1 - .../Worker/WorkflowWorkerTests.cs | 84 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 558ad130..e86c43f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,4 +134,3 @@ jobs: dotnet-repo-path: ${{github.event.pull_request.head.repo.full_name}} version: ${{github.event.pull_request.head.ref}} version-is-repo-ref: true - features-repo-ref: "dotnet-after-1.3.0" diff --git a/tests/Temporalio.Tests/Worker/WorkflowWorkerTests.cs b/tests/Temporalio.Tests/Worker/WorkflowWorkerTests.cs index cf6e9bc5..f4e46b42 100644 --- a/tests/Temporalio.Tests/Worker/WorkflowWorkerTests.cs +++ b/tests/Temporalio.Tests/Worker/WorkflowWorkerTests.cs @@ -5792,6 +5792,90 @@ await ExecuteWorkerAsync( client); } + [Workflow] + public class DetachedCancellationWorkflow + { + public class Activities + { + public TaskCompletionSource WaitingForCancel { get; } = new(); + + public bool CleanupCalled { get; set; } + + [Activity] + public async Task WaitForCancel() + { + WaitingForCancel.SetResult(); + await Task.Delay( + Timeout.Infinite, + ActivityExecutionContext.Current.CancellationToken); + } + + [Activity] + public void Cleanup() => CleanupCalled = true; + } + + [WorkflowRun] + public async Task RunAsync() + { + // Wait forever for cancellation, then cleanup + try + { + await Workflow.ExecuteActivityAsync( + (Activities acts) => acts.WaitForCancel(), + new() { StartToCloseTimeout = TimeSpan.FromMinutes(10) }); + } + catch (Exception e) when (TemporalException.IsCanceledException(e)) + { + // Run cleanup with another token + using var detachedCancelSource = new CancellationTokenSource(); + await Workflow.ExecuteActivityAsync( + (Activities acts) => acts.Cleanup(), + new() + { + StartToCloseTimeout = TimeSpan.FromMinutes(10), + CancellationToken = detachedCancelSource.Token, + }); + // Rethrow + throw; + } + } + } + + [Fact] + public async Task ExecuteWorkflowAsync_DetachedCancellation_WorksProperly() + { + var activities = new DetachedCancellationWorkflow.Activities(); + await ExecuteWorkerAsync( + async worker => + { + // Start workflow + var handle = await Client.StartWorkflowAsync( + (DetachedCancellationWorkflow wf) => wf.RunAsync(), + new(id: $"workflow-{Guid.NewGuid()}", taskQueue: worker.Options.TaskQueue!)); + + // Wait until waiting for cancel + await activities.WaitingForCancel.Task; + + // Send workflow cancel + await handle.CancelAsync(); + + // Confirm canceled + var exc = await Assert.ThrowsAsync( + () => handle.GetResultAsync()); + Assert.IsType(exc.InnerException); + + // Confirm cleanup called + Assert.True(activities.CleanupCalled); + + // Run through replayer to confirm deterministic on replay + var history = await handle.FetchHistoryAsync(); + var replayer = new WorkflowReplayer( + new WorkflowReplayerOptions().AddWorkflow()); + await replayer.ReplayWorkflowAsync(history); + }, + new TemporalWorkerOptions().AddAllActivities(activities)); + } + internal static Task AssertTaskFailureContainsEventuallyAsync( WorkflowHandle handle, string messageContains) {