From 57fe5eb97b76028af7727ecbd3fe8ac7da0b005f Mon Sep 17 00:00:00 2001 From: KnifeLemon Date: Fri, 30 Oct 2026 15:24:02 +0900 Subject: [PATCH 1/2] Feed the stall-watch test from a thread of its own A_program_that_keeps_reporting_runs_well_past_the_stall_limit still failed on CI after #768 (#790's run): its fake output was a Task.Delay loop, which waits for the thread pool, and the other test classes running in parallel can hold that up for over a second, so the watch saw a stall. The output now comes from a dedicated thread, like the real output reader, and the test's continuation runs asynchronously so it never joins its own thread. Co-Authored-By: Claude Opus 5.5 --- tests/Filee.Engines.Tests/StallWatchTests.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/Filee.Engines.Tests/StallWatchTests.cs b/tests/Filee.Engines.Tests/StallWatchTests.cs index d35ffa7b5..1f7df6d9c 100644 --- a/tests/Filee.Engines.Tests/StallWatchTests.cs +++ b/tests/Filee.Engines.Tests/StallWatchTests.cs @@ -18,23 +18,27 @@ public async Task A_program_that_keeps_reporting_runs_well_past_the_stall_limit( var token = TestContext.Current.CancellationToken; // A limit far above the 50 ms between lines: a busy CI runner can delay a 50 ms wait by a few hundred ms. var watch = new StallWatch(TimeSpan.FromSeconds(1)); - var exited = new TaskCompletionSource(); + // Asynchronously: the test must not go on on the output thread, which it joins at the end. + var exited = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var started = Stopwatch.StartNew(); - // "34% Running transforms on e-book..." every 50 ms for three seconds, three times the limit. - var output = Task.Run(async () => + // "34% Running transforms on e-book..." every 50 ms for three seconds, three times the limit. On a thread of its + // own, like the real output reader: a Task.Delay loop waits for the thread pool, which the other test classes + // running in parallel can keep busy for over a second on a CI runner, and the watch then sees a stall. + var output = new Thread(() => { - while (started.Elapsed < TimeSpan.FromSeconds(3)) + while (started.Elapsed < TimeSpan.FromSeconds(3) && !token.IsCancellationRequested) { watch.Touch(); - await Task.Delay(50, token); + Thread.Sleep(50); } exited.SetResult(); - }, token); + }) { IsBackground = true }; + output.Start(); Assert.True(await watch.WaitAsync(exited.Task, token, Interval)); Assert.True(started.Elapsed >= TimeSpan.FromSeconds(3)); - await output; + output.Join(); } [Fact] From 0998e340851b944f3b3daf17857bff8992b799f1 Mon Sep 17 00:00:00 2001 From: KnifeLemon Date: Fri, 30 Oct 2026 15:32:42 +0900 Subject: [PATCH 2/2] Format the stall-watch test's thread initializer Co-Authored-By: Claude Opus 5.5 --- tests/Filee.Engines.Tests/StallWatchTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Filee.Engines.Tests/StallWatchTests.cs b/tests/Filee.Engines.Tests/StallWatchTests.cs index 1f7df6d9c..7e38c8310 100644 --- a/tests/Filee.Engines.Tests/StallWatchTests.cs +++ b/tests/Filee.Engines.Tests/StallWatchTests.cs @@ -33,7 +33,8 @@ public async Task A_program_that_keeps_reporting_runs_well_past_the_stall_limit( Thread.Sleep(50); } exited.SetResult(); - }) { IsBackground = true }; + }) + { IsBackground = true }; output.Start(); Assert.True(await watch.WaitAsync(exited.Task, token, Interval));