From 70de5e9a1bc64b2d7eba968e5c7d9d19de280e79 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Tue, 11 Aug 2026 18:27:24 -0700 Subject: [PATCH 1/2] test: stabilize multi-grain cancellation Wait for every delayed grain call to start before cancelling the shared token so grain-side cancellation evidence cannot be skipped. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../CancellationTokenTests.cs | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/test/Orleans.Runtime.Tests/CancellationTests/CancellationTokenTests.cs b/test/Orleans.Runtime.Tests/CancellationTests/CancellationTokenTests.cs index 7e3613dd153..c7aa292f2c2 100644 --- a/test/Orleans.Runtime.Tests/CancellationTests/CancellationTokenTests.cs +++ b/test/Orleans.Runtime.Tests/CancellationTests/CancellationTokenTests.cs @@ -107,21 +107,44 @@ public async Task GrainTaskCancellation(int delay) public async Task MultipleGrainsTaskCancellation(int delay) { using var cts = new CancellationTokenSource(); - var callId = Guid.NewGuid(); var grains = Enumerable.Range(0, 5).Select(_ => fixture.GrainFactory.GetGrain>(Guid.NewGuid())).ToList(); - var grainTasks = grains.Select(grain => - Assert.ThrowsAnyAsync(() => - grain.LongWaitInterleaving(cts.Token, TimeSpan.FromSeconds(10), callId))) - .ToList(); - cts.CancelAfter(delay); - await Task.WhenAll(grainTasks); - if (delay > 0) + var callIds = grains.Select(_ => Guid.NewGuid()).ToArray(); + var observer = new LongRunningTaskObserver(); + var observerReference = fixture.GrainFactory.CreateObjectReference(observer); + try { - foreach (var grain in grains) + var grainTasks = grains.Select((grain, index) => + delay > 0 + ? grain.LongWaitInterleavingWithStartNotification( + TimeSpan.FromSeconds(10), + callIds[index], + observerReference, + cts.Token) + : grain.LongWaitInterleaving( + cts.Token, + TimeSpan.FromSeconds(10), + callIds[index])) + .ToArray(); + if (delay > 0) { - await WaitForCallCancellation(grain, callId); + await Task.WhenAll(callIds.Select(observer.WaitForCallToStart)); + } + + cts.CancelAfter(delay); + await Task.WhenAll(grainTasks.Select(task => + Assert.ThrowsAnyAsync(() => task))); + if (delay > 0) + { + for (var i = 0; i < grains.Count; i++) + { + await WaitForCallCancellation(grains[i], callIds[i]); + } } } + finally + { + fixture.GrainFactory.DeleteObjectReference(observerReference); + } } [Theory, TestCategory("BVT"), TestCategory("Cancellation")] From 1b228cc3f6dcd4d2ec6cb21000e78f2e3512ee93 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Tue, 11 Aug 2026 18:36:38 -0700 Subject: [PATCH 2/2] test: bound multi-grain cancellation wait Await the independent grain cancellation evidence checks concurrently so a regression remains bounded by one timeout. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 13b77a0c-9317-4fdc-b06c-cef2ea19fd63 --- .../CancellationTests/CancellationTokenTests.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/Orleans.Runtime.Tests/CancellationTests/CancellationTokenTests.cs b/test/Orleans.Runtime.Tests/CancellationTests/CancellationTokenTests.cs index c7aa292f2c2..06feb3c0512 100644 --- a/test/Orleans.Runtime.Tests/CancellationTests/CancellationTokenTests.cs +++ b/test/Orleans.Runtime.Tests/CancellationTests/CancellationTokenTests.cs @@ -135,10 +135,8 @@ await Task.WhenAll(grainTasks.Select(task => Assert.ThrowsAnyAsync(() => task))); if (delay > 0) { - for (var i = 0; i < grains.Count; i++) - { - await WaitForCallCancellation(grains[i], callIds[i]); - } + await Task.WhenAll(grains.Select((grain, index) => + WaitForCallCancellation(grain, callIds[index]))); } } finally