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>
|
Tagging subscribers to this area: @dotnet/area-system-diagnostics-process |
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
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
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>
…emove unnecessary handleCount > 0 check Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/8f476c7f-4f16-4bdf-b34d-93e9e161ecdf Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
|
/azp run runtime-libraries-coreclr outerloop-windows |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Dump generation on crashes is an environment or machine-wide setting that is enabled on Helix machines. If you do not see crash dumps being generated locally, it says nothing about what's going to happen in Helix. |
Thanks, I was not aware of that as I can see these dumps being generated on Linux when I run the tests locally |
- combining handle inheritance restriction with KillOnParentExit - combining invalid standard handles, handle inheritance restriction with KillOnParentExit
…lid, there is no need to use PROC_THREAD_ATTRIBUTE_HANDLE_LIST
…tional and expected
…arent-exit-property # Conflicts: # src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs
| 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.
@jkotas I added that after reading https://github.com/dotnet/arcade/blob/d3eca2571cf5822e11fda70f12747cb3f2c19cfc/src/Microsoft.DotNet.RemoteExecutor/src/RemoteInvokeHandle.cs#L152
I've also asked offline on teams to ensure this is the right approach
src/libraries/System.Diagnostics.Process/tests/KillOnParentExitTests.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
/azp run runtime-libraries-coreclr outerloop-windows |
|
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs:519
BuildProcThreadAttributeListcan be called withattributeCount == 0(e.g.,InheritedHandlesis non-null buthandleCountends up 0 andkillOnParentExitis false). In that case we still callInitializeProcThreadAttributeListand allocate a buffer even though no attributes will be added; on some Windows versions this call may fail with an invalid-parameter error. Consider short-circuiting whenattributeCount == 0(leaveattributeListBuffernull and skip initialization), and only setEXTENDED_STARTUPINFO_PRESENT/STARTUPINFOEXwhen there is at least one attribute to apply.
private static unsafe void BuildProcThreadAttributeList(
IntPtr* handlesToInherit,
int handleCount,
bool killOnParentExit,
int attributeCount,
ref void* attributeListBuffer,
ref IntPtr* jobHandles)
{
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());
}
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
🤖 Copilot Code Review — PR #126699Note This review was generated by Copilot (Claude Opus 4.6), with additional analysis from GPT-5.2. Holistic AssessmentMotivation: The feature is well-motivated — reliably terminating child processes when the parent exits is a common requirement (build systems, test harnesses, daemon supervisors). Using Windows Job Objects with Approach: The implementation is well-structured with two code paths: atomic Summary: Detailed Findings
|
| Line | Variable | Status |
|---|---|---|
| 66 | limitInheritanceStr |
✅ correct |
| 97 | limitInherianceStr |
❌ typo (missing 't') |
| 135 | limitInherianceStr |
❌ typo (missing 't') |
Merge-blocking? No — local variable name, doesn't affect behavior.
✅ Test Quality — Comprehensive coverage
The test suite is well-designed:
- Default value, setter, and validation (UseShellExecute conflict) tests
- Three kill scenarios: parent exits normally, parent is killed, parent crashes (via
Marshal.WriteInt32(IntPtr.Zero, 42)) [Theory]with[InlineData]testing combinations ofenabledxrestrictInheritanceHELIX_WORKITEM_UPLOAD_ROOTcleared for the crash test to avoid spurious dump uploadsVerifyProcessIsRunningusesEnvironment.TickCount64-based polling — correct for timeout calculations- Existing tests extended with
killOnParentExitparameter (TestUserCredentialsPropertiesOnWindows,ProcessStartedWithInvalidHandles_CanStartChildProcessWithDerivedInvalidHandles) - Related test improvements:
[Fact]to[ConditionalFact]and[SkipOnPlatform]additions for iOS/tvOS/MacCatalyst
💡 Test Timeout — 10s may be tight on loaded CI
The VerifyProcessIsRunning method uses a 10-second timeout for waiting for child process termination. On loaded Helix machines, job object cleanup after parent termination could potentially take longer. The dotnet/runtime testing guidelines suggest generous timeouts for stress pipelines. This is a low-probability concern but worth noting.
Review models: Claude Opus 4.6 (primary), GPT-5.2 (secondary). Both models agreed on the key findings.
Generated by Code Review for issue #126699 · ◷
|
/azp run runtime-libraries-coreclr outerloop-windows |
|
Azure Pipelines failed to run 1 pipeline(s). |
| // Determine the number of attributes we need to set in the proc thread attribute list. | ||
| int attributeCount = 0; | ||
|
|
||
| // When InheritedHandles is set but handleCount is 0 (all standard handles are invalid), | ||
| // we pass bInheritHandles=false to CreateProcess and there is no need to use PROC_THREAD_ATTRIBUTE_HANDLE_LIST. | ||
| if (restrictInheritedHandles && handleCount > 0) | ||
| { | ||
| attributeCount++; // PROC_THREAD_ATTRIBUTE_HANDLE_LIST | ||
| } | ||
| if (killOnParentExit) | ||
| { | ||
| attributeCount++; // PROC_THREAD_ATTRIBUTE_JOB_LIST | ||
| } | ||
|
|
There was a problem hiding this comment.
Should this be in BuildProcThreadAttributeList since it is coupled with the logic in that method?
|
|
||
| // When InheritedHandles is set but handleCount is 0 (all standard handles are invalid), | ||
| // we pass bInheritHandles=false to CreateProcess and there is no need to use PROC_THREAD_ATTRIBUTE_HANDLE_LIST. | ||
| if (restrictInheritedHandles && handleCount > 0) |
There was a problem hiding this comment.
These is a mismatch between this condition and the condition used in BuildProcThreadAttributeList. This condition uses restrictInheritedHandles, but the same condition in BuildProcThreadAttributeList does not. I am not able to tell whether it is a real problem, but the two conditions should be identical.
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 terminatedBuildProcThreadAttributeListto 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,ResumeThreadJOBOBJECT_EXTENDED_LIMIT_INFORMATION,IO_COUNTERS,JOBOBJECTINFOCLASSstructs/enumsSafeJobHandle,PROC_THREAD_ATTRIBUTE_JOB_LISTconstantValidation:
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)KillOnParentExit_WithUserCredentials— exercises theCreateProcessWithLogonW+ suspended + job assignment path using a Windows test account (admin/OuterLoop)VerifyProcessIsRunninguses polling withEnvironment.TickCount64and a 10-second timeout to avoid flaky assertions from PID reuse on Windows