Implement ProcessStartInfo.KillOnParentExit for Windows#126699
Implement ProcessStartInfo.KillOnParentExit for Windows#126699
Conversation
Add KillOnParentExit property to ProcessStartInfo that terminates child processes when the parent process exits. Windows implementation uses Job Objects with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag. For CreateProcess: uses PROC_THREAD_ATTRIBUTE_JOB_LIST for atomic assignment. For CreateProcessWithLogonW: starts suspended, assigns to job, then resumes. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/61612ca1-930c-4836-8554-d143d05c8321 Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot address my feedback
...ries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs
Outdated
Show resolved
Hide resolved
...ries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs
Outdated
Show resolved
Hide resolved
...ries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/KillOnParentExitTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/KillOnParentExitTests.cs
Outdated
Show resolved
Hide resolved
…py path, add user credentials test, fix pid reporting via stdout, remove nop UseShellExecute test Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/c4dd95b4-cd8e-4740-83f9-7358444c04ce Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
- simplify the tests - avoid duplication!!
There was a problem hiding this comment.
Pull request overview
Implements Windows support for ProcessStartInfo.KillOnParentExit by using Windows Job Objects to ensure spawned processes are terminated when the parent exits.
Changes:
- Added
ProcessStartInfo.KillOnParentExitAPI surface (implementation + ref assembly) and validation againstUseShellExecute. - Implemented Windows job-object based process start paths (CreateProcess job-list attribute, and CreateProcessWithLogonW suspended + assign + resume).
- Added Windows-focused tests validating the behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj | Includes the new KillOnParentExit test file in the test project. |
| src/libraries/System.Diagnostics.Process/tests/ProcessStartInfoTests.cs | Extends existing Windows credential tests to cover KillOnParentExit on that path. |
| src/libraries/System.Diagnostics.Process/tests/KillOnParentExitTests.cs | Adds new Windows tests validating KillOnParentExit behavior under various parent-exit scenarios. |
| src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartInfo.cs | Adds the KillOnParentExit property, docs, and validation with UseShellExecute. |
| src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj | Links in the new Windows job-object interop file. |
| src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx | Adds a resource string for the KillOnParentExit + UseShellExecute validation. |
| src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs | Windows implementation: shared job handle, job-list attribute support, and logon path job assignment. |
| src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs | Adds the KillOnParentExit property to the reference assembly surface. |
| src/libraries/Common/src/Interop/Windows/Kernel32/Interop.JobObjects.cs | Adds required job-object P/Invokes/structs/constants. |
| src/libraries/Common/src/Interop/Windows/Advapi32/Interop.ProcessOptions.cs | Adds CREATE_SUSPENDED for the CreateProcessWithLogonW suspended-start path. |
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartInfo.cs
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs
Show resolved
Hide resolved
...ries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs
Outdated
Show resolved
Hide resolved
...ries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/KillOnParentExitTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/KillOnParentExitTests.cs
Outdated
Show resolved
Hide resolved
This comment has been minimized.
This comment has been minimized.
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot address my feedback
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs
Show resolved
Hide resolved
...ries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs
Outdated
Show resolved
Hide resolved
...ries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartInfo.cs
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/KillOnParentExitTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/KillOnParentExitTests.cs
Outdated
Show resolved
Hide resolved
…sumeThread return, remove unused using, improve VerifyProcessIsRunning polling Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/06c58cfa-bdef-4f35-9345-9ab03e7dc542 Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartInfo.cs
Show resolved
Hide resolved
| remoteInvokeOptions.StartInfo.RedirectStandardError = true; | ||
| remoteInvokeOptions.StartInfo.RedirectStandardInput = true; | ||
| // Don't create (and upload) a memory dump for this test, as AV is intentional and expected. | ||
| remoteInvokeOptions.StartInfo.Environment["HELIX_WORKITEM_UPLOAD_ROOT"] = null; |
There was a problem hiding this comment.
This will clear the env variable for the child process. Where is the code in the child process that is going to read it?
There was a problem hiding this comment.
No code in the child reads it — removed the line in 04d67a9.
There was a problem hiding this comment.
@jkotas after reading RemoteExecutor code again, I believe that it's going to capture the dump only if the child process does not exit on it's own before RemoteInvokeHandle gets disposed/finalized:
The child process crashes, so it's not a problem.
The grand child is created with CreateProcessLong, which uses this method to create it:
runtime/src/libraries/System.Diagnostics.Process/tests/ProcessTestBase.cs
Lines 95 to 102 in 280fb74
Since it disposes RemoteInvokeHandle, I currently don't see a way to create a dump for this test. I am going to open a RemoteExecutor issue and extend it with an ability to disable it properly via the option bag.
There was a problem hiding this comment.
I've added the defensive env var back and added it to Outerloop based on feedback I got offline
…nfo.hProcess vs procSH consistency Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/09cb9d8c-1881-4dc2-80ac-f52f2a02112e Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
I'm unable to access the AzDO pipeline logs from my environment. Could you share the specific test failure details? The code changes in this commit (4fdc31a) address the other review feedback — if the test failures are related to the nuint/consistency issues, they should be resolved now. |
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot address my feedback
...ries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs
Outdated
Show resolved
Hide resolved
…effective HELIX env var Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/a6c175e9-c76c-45ff-978c-cf037751d56b Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/fbffe836-d31a-44ba-a420-e7f63c24df12 Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
This test is most likely flaky (sending signals on Windows...) I've introduced it in #126632 and it has failed once there The logs: I was unable to reproduce it locally and after a long debate with few models I got to the conclusion that it could be caused by PID reuse, so I've pushed this fix. The CI was ofc green, but it does not mean that the test is not flaky. |
src/libraries/System.Diagnostics.Process/tests/KillOnParentExitTests.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs:524
- If
InitializeProcThreadAttributeList(attributeListBuffer, ...)fails,attributeListBufferis still non-null and thefinallyblock will callDeleteProcThreadAttributeListon a buffer that was never successfully initialized. That’s undefined per Win32 contract and could lead to AVs in low-memory/error paths. Consider only assigning toattributeListBufferafter successful initialization (or track aninitializedflag) so cleanup callsDeleteProcThreadAttributeListonly when initialization succeeded; otherwise just free the allocated memory.
nuint size = 0;
Interop.Kernel32.InitializeProcThreadAttributeList(null, attributeCount, 0, ref size);
attributeListBuffer = NativeMemory.Alloc(size);
if (!Interop.Kernel32.InitializeProcThreadAttributeList(attributeListBuffer, attributeCount, 0, ref size))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
🤖 Copilot Code Review — PR #126699Note This review was AI/Copilot-generated. Holistic AssessmentMotivation: Justified — these are code review follow-up fixes addressing maintainer feedback (jkotas) on the recently-merged Approach: Sound — all changes are incremental improvements that align with dotnet/runtime conventions: SafeHandle-based P/Invoke parameters, correct unsigned return types for Windows DWORD APIs, modern Summary: ✅ LGTM. All changes are correct and well-motivated. No new public API surface. No correctness, safety, or performance concerns found. Reviewed by Claude Opus 4.6 and Claude Sonnet 4.5 (GPT-5.4 also consulted but did not complete in time). Detailed Findings✅ SafeProcessHandle for AssignProcessToJobObject — Correctness improvementChanging ✅ ResumeThread return type and error check — Bug fixThe Windows ✅ ResumeThread in separate file — No duplicate concern
✅ UIntPtr → nuint — Clean modernization
✅ AssignJobAndResumeThread signature — Cleaner interfacePassing only ✅ Test changes — All appropriate
💡 Out-of-scope: Test
|
Description
Implements the
ProcessStartInfo.KillOnParentExitproperty for Windows, which terminates child processes when the parent process exits. This is the Windows part of theKillOnParentExitfeature.API approved at #125838 (comment)
Changes Made
New API surface:
KillOnParentExitproperty toProcessStartInfowith[SupportedOSPlatform("windows")]attribute (linux support will be added in upcoming PRs)ref/System.Diagnostics.Process.cs) with matching[SupportedOSPlatformAttribute("windows")]JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEWindows implementation (
SafeProcessHandle.Windows.cs):SafeJobHandlewithJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEflag, shared across all child processes withKillOnParentExit=trueCreateProcesspath (no user credentials): usesPROC_THREAD_ATTRIBUTE_JOB_LISTin the extended startup info for atomic job assignment at process creationCreateProcessWithLogonWpath (with user credentials): starts the process suspended, assigns it to the job object viaAssignProcessToJobObject, then resumes the thread only on the happy path — on failure, the suspended process is terminatedResumeThreadreturn value is checked; failure throwsWin32Exceptionand the suspended process is terminatedAssignJobAndResumeThreadtakes(IntPtr hThread, SafeProcessHandle procSH)and usesprocSHconsistently for all process handle operations (no mixing ofprocessInfo.hProcessandprocSH)BuildProcThreadAttributeListto support bothPROC_THREAD_ATTRIBUTE_HANDLE_LISTandPROC_THREAD_ATTRIBUTE_JOB_LISTattributesstring.IsNullOrEmpty(startInfo.UserName)for defensive checks when determining the code pathP/Invoke definitions:
Interop.JobObjects.cs:CreateJobObjectW,SetInformationJobObject,AssignProcessToJobObject(acceptsSafeProcessHandle),JOBOBJECT_EXTENDED_LIMIT_INFORMATION,IO_COUNTERS,JOBOBJECTINFOCLASSstructs/enums,SafeJobHandle,PROC_THREAD_ATTRIBUTE_JOB_LISTconstantInterop.ResumeThread.cs:ResumeThreadP/Invoke returninguint(matching nativeDWORD) in its own dedicated file (following the pattern ofInterop.TerminateProcess.cs,Interop.GetExitCodeProcess.cs, etc.)IO_COUNTERSandJOBOBJECT_EXTENDED_LIMIT_INFORMATIONusenuintfor fields representing numerical values (sizes, limits, affinity masks) per interop conventionValidation:
ProcessStartInfo.ThrowIfInvalidto throwInvalidOperationExceptionwhenKillOnParentExitis combined withUseShellExecuteKillOnParentExitCannotBeUsedWithUseShellExecutestring resourceTests (
KillOnParentExitTests.cs):KillOnParentExit_DefaultsToFalse/KillOnParentExit_CanBeSetToTrue— property basicsKillOnParentExit_WithUseShellExecute_Throws— validation testKillOnParentExit_ProcessStartsAndExitsNormally— smoke testKillOnParentExit_KillsTheChild_WhenParentExits— verifies child is killed (enabled=true) or survives (enabled=false) on normal parent exit, reports grandchild pid via stdoutKillOnParentExit_KillsTheChild_WhenParentIsKilled— same when parent is forcefully killedKillOnParentExit_KillsTheChild_WhenParentCrashes— same when parent crashes (access violation)ProcessStartInfoTests.TestUserCredentialsPropertiesOnWindows(admin/OuterLoop)