Uh oh!
There was an error while loading. Please reload this page.
Fix Process.WaitForExit hang when grandchild process keeps redirected pipe open - #125732
Fix Process.WaitForExit hang when grandchild process keeps redirected pipe open#125732adamsitnik with Copilot wants to merge 9 commits into
Conversation
Add CancelDueToProcessExit and CancelDueToProcessExitAsync methods to AsyncStreamReader that wait a timeout for the read task to finish and cancel it if it doesn't. Replace all EOF.GetAwaiter().GetResult() and EOF.WaitAsync() calls in WaitForExitCore (Unix/Windows) and WaitForExitAsync with the new timeout-based methods. Use 300ms default when no timeout is provided. Add Theory test reproducing the hang scenario with a grandchild process that sleeps for 8 hours. Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
… grandchild process dispatch Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot please address my feedback
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Tagging subscribers to this area: @dotnet/area-system-diagnostics-process |
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
adamsitnik
commented
Mar 18, 2026
@copilot please invoke the code-review skill and post your analysis/results as a comment on this PR |
🤖 Copilot Code Review — PR #125790Holistic AssessmentMotivation: The problem is real and well-documented — Approach: The fix introduces a timeout-based cancellation mechanism in Summary: Detailed Findings
|
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot address my feedback
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…thods parameterless, remove EOF Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot please address my feedback
Uh oh!
There was an error while loading. Please reload this page.
…ss from callers Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.
…xitAsync local function Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
adamsitnik
commented
Mar 21, 2026
@copilot please invoke the code-review skill and post your analysis/results as a comment on this PR |
🤖 Copilot Code Review — PR #125790Note This review was AI/Copilot-generated. Models consulted: Claude Opus 4.6 (primary), Gemini 2.5 Pro, GPT-4.1. Holistic AssessmentMotivation: The problem is real and well-documented — Approach: The fix replaces the unbounded Summary: ✅ LGTM. The code is correct, thread-safe, and addresses the root cause. All prior review feedback has been addressed. No blocking issues remain. Detailed Findings✅ Correctness — |
Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it. |
Description
Process.WaitForExit()hangs indefinitely when stdout/stderr is redirected and a grandchild process inherits the pipe handle. The root cause: after the child process exits,AsyncStreamReaderblocks forever onEOF.GetAwaiter().GetResult()because the grandchild still holds the write end of the pipe open.Changes
AsyncStreamReader.cs— Added parameterlessCancelDueToProcessExit()andCancelDueToProcessExitAsync(CancellationToken)methods that use a privateStreamDrainDefaultTimeoutMsconstant (300ms) internally. These wait up to the default timeout for_readToBufferTaskto complete, then cancel via_cts.Cancel()if it hasn't. The async variant accepts aCancellationTokento allow callers to cancel the stream drain phase. Removed the now-unusedEOFproperty.Process.Unix.cs/Process.Windows.cs— Replaced_output?.EOF.GetAwaiter().GetResult()withCancelDueToProcessExit(). Now drains streams for all exited processes (not justTimeout.Infinite).Process.cs— UpdatedWaitForExitAsyncto useCancelDueToProcessExitAsync(cancellationToken)instead ofEOF.WaitAsync(cancellationToken), forwarding the caller's cancellation token to the stream drain phase.ProcessWaitingTests.cs— Added[Theory]covering all fourWaitForExitoverloads: spawns a child with redirected output that usesCreateProcessto create a sleeping grandchild, asserts WaitForExit completes in <2s.💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.