From a77b2039472445f795246027f393b24daf9b6cb8 Mon Sep 17 00:00:00 2001 From: Peter Shrosbree Date: Sun, 11 Oct 2026 21:49:34 -0700 Subject: [PATCH] Give the nested process chain in ProcessWaitingTests depth-proportional wait budgets (#135608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #118977 ## Problem `ProcessWaitingTests.WaitChain` and `WaitAsyncChain` start a four-deep chain of `RemoteExecutor` processes, and every level waits with the same `WaitInMS`. The root's wait has to cover the start-up, work and exit of all four processes while the leaf's covers one, so the root has the leaf's budget while covering four lifetimes. ## Change Give each waiter `WaitInMS` per process beneath it: leaf 1×, then 2×, 3× and 4× at the root. > [!NOTE] > This PR was prepared with AI assistance (Anthropic Claude, checked by OpenAI Codex) at my request. I reviewed the change and the measurements before opening it. --- .../tests/ProcessWaitingTests.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessWaitingTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessWaitingTests.cs index 8ef602113be272..816a5e41635a39 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessWaitingTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessWaitingTests.cs @@ -541,6 +541,10 @@ public async Task WaitForExitAsync_AfterProcessExit_ShouldConsumeOutputDataRecei Assert.Equal(1, linesReceived); } + // A waiter in a nested process chain covers the start-up, work and exit of every process beneath it, so its + // budget grows with the number of processes below. + private static int ChainWaitInMS(int processesBelow) => WaitInMS * processesBelow; + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void WaitChain() { @@ -552,19 +556,19 @@ public void WaitChain() { Process child3 = CreateProcess(() => RemoteExecutor.SuccessExitCode); child3.Start(); - Assert.True(child3.WaitForExit(WaitInMS)); + Assert.True(child3.WaitForExit(ChainWaitInMS(1))); return child3.ExitCode; }); child2.Start(); - Assert.True(child2.WaitForExit(WaitInMS)); + Assert.True(child2.WaitForExit(ChainWaitInMS(2))); return child2.ExitCode; }); child1.Start(); - Assert.True(child1.WaitForExit(WaitInMS)); + Assert.True(child1.WaitForExit(ChainWaitInMS(3))); return child1.ExitCode; }); root.Start(); - Assert.True(root.WaitForExit(WaitInMS)); + Assert.True(root.WaitForExit(ChainWaitInMS(4))); Assert.Equal(RemoteExecutor.SuccessExitCode, root.ExitCode); } @@ -579,7 +583,7 @@ public async Task WaitAsyncChain() { Process child3 = CreateProcess(() => RemoteExecutor.SuccessExitCode); child3.Start(); - using (var cts = new CancellationTokenSource(WaitInMS)) + using (var cts = new CancellationTokenSource(ChainWaitInMS(1))) { await child3.WaitForExitAsync(cts.Token); Assert.True(child3.HasExited); @@ -588,7 +592,7 @@ public async Task WaitAsyncChain() return child3.ExitCode; }); child2.Start(); - using (var cts = new CancellationTokenSource(WaitInMS)) + using (var cts = new CancellationTokenSource(ChainWaitInMS(2))) { await child2.WaitForExitAsync(cts.Token); Assert.True(child2.HasExited); @@ -597,7 +601,7 @@ public async Task WaitAsyncChain() return child2.ExitCode; }); child1.Start(); - using (var cts = new CancellationTokenSource(WaitInMS)) + using (var cts = new CancellationTokenSource(ChainWaitInMS(3))) { await child1.WaitForExitAsync(cts.Token); Assert.True(child1.HasExited); @@ -606,7 +610,7 @@ public async Task WaitAsyncChain() return child1.ExitCode; }); root.Start(); - using (var cts = new CancellationTokenSource(WaitInMS)) + using (var cts = new CancellationTokenSource(ChainWaitInMS(4))) { await root.WaitForExitAsync(cts.Token); Assert.True(root.HasExited);