You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Make Process.Run methods kill the entire process tree on timeout or cancellation - #136513
Make all Process.Run* overloads terminate the launched process tree on timeout or cancellation using Process.Kill(entireProcessTree: true).
Apply tree cleanup during output capture and the subsequent exit wait.
Preserve existing timeout/cancellation reporting, exit codes and signals, successful-completion behavior, and rejection of stream redirection by non-capturing Run methods.
Keep the implementation in Process.Scenarios.cs, without changing SafeProcessHandle, ProcessManager, or platform-specific tree termination.
Update XML documentation and add tests covering both overload forms, synchronous timeouts, asynchronous cancellation, and capture before and after EOF.
This is an intentional behavioral breaking change: descendants that previously survived timeout or cancellation are now terminated. Callers that need descendants to survive should use Process.Start and manage termination explicitly.
Validation
From src\libraries\System.Diagnostics.Process, on Windows x64:
D:\projects\runtime\.dotnet\dotnet.exe build — passed for all configured targets, with zero warnings or errors, including after the final private-helper rename.
D:\projects\runtime\.dotnet\dotnet.exe build /t:Test tests\System.Diagnostics.Process.Tests.csproj — 716 total: 713 passed, 2 skipped, 1 failed. All 12 tree-cleanup cases and 6 stream-redirection validation cases passed. This full run preceded the final name-only private-helper rename; tests were not rerun after it.
The sole failure, ProcessHandlesTests.ChildProcess_CanBreakAwayFromJob(killOnParentExit: False), is the pre-existing native job-breakaway access-denied failure, also reproduced using the unchanged Release testhost.
The 12 tree-cleanup regression cases were previously run against unmodified main; all failed because descendants remained running.
Apply whole-process-tree cleanup to all Run helpers while preserving public SafeProcessHandle wait behavior. Cover both overload forms and cleanup during capture and after output closes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7ded16ff-274e-41d3-b9ec-2bb8643062c7
Implement tree cleanup using SafeProcessHandle and native process queries instead of calling back into Process. Share the lower-level implementation with Process.Kill and cover fetched-by-ID handles.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7ded16ff-274e-41d3-b9ec-2bb8643062c7
Restore the original SafeProcessHandle, ProcessManager, and platform implementations. Use Process.Kill(entireProcessTree: true) for Run timeout and cancellation cleanup while retaining the regression tests and stream-redirection validation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7ded16ff-274e-41d3-b9ec-2bb8643062c7
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Termination failures can leave timeout or cancellation paths waiting indefinitely, while the tests contain Debug-runtime watchdog timing errors and a scheduling-dependent capture assertion.
Updates Process.Run* to terminate descendants as well as the launched process on timeout or cancellation, using the existing process-tree termination support.
Changes:
Applies tree termination during output capture and subsequent exit waits.
Updates XML documentation and exposes cancellation state internally on both platform-specific safe handles.
Adds coverage for tree cleanup, redirected-stream rejection, and tree termination through a process retrieved by ID.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Cleanup failures can mask original capture exceptions, and descendant-cleanup tests omit synchronous timeouts, post-EOF exit waits, and filename overloads.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
On Unix, background descendants can keep redirected stdout open and survive cleanup after the shell exits because process-tree discovery skips an exited root.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Track launched descendants after Unix root process exits
On Unix, a shell can exit while a background child keeps redirected stdout open, so capture remains pending until timeout or cancellation. Cleanup then leaves that child running: Process.Unix.cs:170–173 skips tree discovery for an exited root, whose children have already been reparented. To cover this case, retain a way to identify and terminate the launched descendants from startup rather than relying solely on the root PID at cleanup time.
On Unix, background descendants can keep redirected stdout open and survive cleanup after the shell exits because process-tree discovery skips an exited root.
Based on my understanding of available Unix sys-calls and mechanisms, there is nothing we can do about it ;(
The reason will be displayed to describe this comment to others. Learn more.
Kill(entireProcessTree: true) is a ton on code. This change will add about ~400kB to a simple NAOT compiled app that just starts process using the simple APIs that this PR is changing (on Windows). Is this the right tradeoff?
The reason will be displayed to describe this comment to others. Learn more.
Kill(entireProcessTree: true) is a ton on code. This change will add about ~400kB to a simple NAOT compiled app that just starts process using the simple APIs that this PR is changing (on Windows).
As of today, it is. With #126273, we could make it trivial, especially here where we know that we are about to start a process and we know whether we may need to kill entire tree or not (timeout/cancelable token was provided). This is what some of our 1P customers do (I keep an eye on code that was not able to remove all custom p/invokes after migrating to the new APIs).
Is this the right tradeoff?
We are going to increase the size on disk, but on the other hand we are going to protect some of our customers from hard to diagnose issues. I hate regressing any kind of benchmarks, but here I think it's the right thing to do. Especially since we can eliminate this regression (in a follow up PR) and make the whole operation faster and atomic (at the cost of putting all processes into a Job).
The reason will be displayed to describe this comment to others. Learn more.
This is what some of our 1P customers do
We should be also asking how many are not doing this and would be actually broken by this change. This change can cut both ways - both on functionality and/or on performance.
Are there example in other ecosystems where simple process APIs try to kill the whole process tree on failure?
The reason will be displayed to describe this comment to others. Learn more.
We should be also asking how many are not doing this and would be actually broken by this change. This change can cut both ways - both on functionality and/or on performance.
I think that 99.9% would benefit and only 0.1% could be broken for scenarios like spawning background compiler process for performance purposes.
Are there example in other ecosystems where simple process APIs try to kill the whole process tree on failure?
I've used the Office Copilot Researcher to answer your quesiton:
@jkotas which leads me to a conclusion that this most likely should be controlled via a new, optional bool parameter:
The reason will be displayed to describe this comment to others. Learn more.
which leads me to a conclusion that this most likely should be controlled via a new, optional bool parameter:
Yes, it sounds like that to me as well. It is unfortunate that we have made silent a bool - it would be nicer if it is an enum.
If we do this, we should look at trimming the implementation to be leaner.
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Make all
Process.Run*overloads terminate the launched process tree on timeout or cancellation usingProcess.Kill(entireProcessTree: true).Process.Scenarios.cs, without changingSafeProcessHandle,ProcessManager, or platform-specific tree termination.This is an intentional behavioral breaking change: descendants that previously survived timeout or cancellation are now terminated. Callers that need descendants to survive should use
Process.Startand manage termination explicitly.Validation
From
src\libraries\System.Diagnostics.Process, on Windows x64:D:\projects\runtime\.dotnet\dotnet.exe build— passed for all configured targets, with zero warnings or errors, including after the final private-helper rename.D:\projects\runtime\.dotnet\dotnet.exe build /t:Test tests\System.Diagnostics.Process.Tests.csproj— 716 total: 713 passed, 2 skipped, 1 failed. All 12 tree-cleanup cases and 6 stream-redirection validation cases passed. This full run preceded the final name-only private-helper rename; tests were not rerun after it.ProcessHandlesTests.ChildProcess_CanBreakAwayFromJob(killOnParentExit: False), is the pre-existing native job-breakaway access-denied failure, also reproduced using the unchanged Release testhost.git --no-pager diff --check— passed.Unix targets were compiled but not executed.
Resolves #136466
Note
This pull request was created with GitHub Copilot.