From 801679a59396d0559cf967ac8a2f7acd2271d8a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:10:37 +0000 Subject: [PATCH 1/5] Add JOB_OBJECT_LIMIT_BREAKAWAY_OK to kill-on-parent-exit job and add test Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../Windows/Kernel32/Interop.CreateProcess.cs | 1 + .../Windows/Kernel32/Interop.JobObjects.cs | 1 + .../SafeHandles/SafeProcessHandle.Windows.cs | 2 +- .../tests/ProcessHandlesTests.Windows.cs | 71 +++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateProcess.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateProcess.cs index ac2f7dca331b06..4a1fcb5ccee46d 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateProcess.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateProcess.cs @@ -58,6 +58,7 @@ internal struct STARTUPINFO } internal const int PROC_THREAD_ATTRIBUTE_HANDLE_LIST = 0x00020002; + internal const int CREATE_BREAKAWAY_FROM_JOB = 0x01000000; internal const int EXTENDED_STARTUPINFO_PRESENT = 0x00080000; [StructLayout(LayoutKind.Sequential)] diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.JobObjects.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.JobObjects.cs index 3fd311578a3c92..b23709e05917b8 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.JobObjects.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.JobObjects.cs @@ -21,6 +21,7 @@ public SafeJobHandle() : base(true) { } internal static partial SafeJobHandle CreateJobObjectW(IntPtr lpJobAttributes, IntPtr lpName); internal const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000; + internal const uint JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800; internal enum JOBOBJECTINFOCLASS { diff --git a/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs b/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs index c3f772c26c4a39..1d8525da6e2c41 100644 --- a/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs @@ -98,7 +98,7 @@ private static bool TryOpenCore(int processId, [NotNullWhen(true)] out SafeProce private static unsafe Interop.Kernel32.SafeJobHandle CreateKillOnParentExitJob() { Interop.Kernel32.JOBOBJECT_EXTENDED_LIMIT_INFORMATION limitInfo = default; - limitInfo.BasicLimitInformation.LimitFlags = Interop.Kernel32.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + limitInfo.BasicLimitInformation.LimitFlags = Interop.Kernel32.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | Interop.Kernel32.JOB_OBJECT_LIMIT_BREAKAWAY_OK; Interop.Kernel32.SafeJobHandle jobHandle = Interop.Kernel32.CreateJobObjectW(IntPtr.Zero, IntPtr.Zero); if (jobHandle.IsInvalid || !Interop.Kernel32.SetInformationJobObject( diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs index d7188e0f63297a..e7668efcb4d775 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs @@ -159,6 +159,77 @@ public unsafe void StartWithCallback_CreateProcess_CanRedirectOutput() Assert.Equal(0, process.ExitCode); } + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [InlineData(true)] + [InlineData(false)] + public unsafe void ChildProcess_CanBreakAwayFromJob(bool killOnParentExit) + { + using Process process = CreateProcess(arg => + { + using (Process grandChild = CreateProcess(() => + { + return RemoteExecutor.SuccessExitCode; + })) + { + grandChild.StartInfo.KillOnParentExit = bool.Parse(arg); + grandChild.StartInfo.RedirectStandardOutput = true; + + ProcessStartInfo grandChildStartInfo = grandChild.StartInfo; + + using Process started = WindowsProcessStartArguments.Start(grandChildStartInfo, (WindowsProcessStartArguments args) => + { + Interop.Kernel32.STARTUPINFOEX startupInfoEx = default; + Interop.Kernel32.PROCESS_INFORMATION processInfo = default; + Interop.Kernel32.SECURITY_ATTRIBUTES unused_SecAttrs = default; + + startupInfoEx.StartupInfo.cb = sizeof(Interop.Kernel32.STARTUPINFOEX); + startupInfoEx.StartupInfo.hStdInput = args.StandardInput; + startupInfoEx.StartupInfo.hStdOutput = args.StandardOutput; + startupInfoEx.StartupInfo.hStdError = args.StandardError; + startupInfoEx.StartupInfo.dwFlags = Interop.Advapi32.StartupInfoOptions.STARTF_USESTDHANDLES; + + bool retVal = Interop.Kernel32.CreateProcess( + null, + args.Arguments, + ref unused_SecAttrs, + ref unused_SecAttrs, + bInheritHandles: true, + Interop.Kernel32.CREATE_BREAKAWAY_FROM_JOB | Interop.Kernel32.EXTENDED_STARTUPINFO_PRESENT, + args.EnvironmentVariables, + null, + &startupInfoEx, + &processInfo + ); + + if (!retVal) + { + throw new Win32Exception(); + } + + Interop.Kernel32.CloseHandle(processInfo.hThread); + + return processInfo.hProcess; + }); + + started.WaitForExit(WaitInMS); + return started.ExitCode; + } + }, killOnParentExit.ToString()); + + process.StartInfo.KillOnParentExit = killOnParentExit; + process.Start(); + + try + { + process.WaitForExit(WaitInMS); + Assert.Equal(RemoteExecutor.SuccessExitCode, process.ExitCode); + } + finally + { + process.Kill(); + } + } + private unsafe int RunWithInvalidHandles(ProcessStartInfo startInfo) { const nint INVALID_HANDLE_VALUE = -1; From c77b0f096d80fcd29567665b28cb922da3f45993 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:55:24 +0000 Subject: [PATCH 2/5] Apply reviewer suggestion: remove KillOnParentExit from grandchild process Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../tests/ProcessHandlesTests.Windows.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs index e7668efcb4d775..11f4ec728e400b 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs @@ -164,16 +164,13 @@ public unsafe void StartWithCallback_CreateProcess_CanRedirectOutput() [InlineData(false)] public unsafe void ChildProcess_CanBreakAwayFromJob(bool killOnParentExit) { - using Process process = CreateProcess(arg => + using Process process = CreateProcess(() => { using (Process grandChild = CreateProcess(() => { return RemoteExecutor.SuccessExitCode; })) { - grandChild.StartInfo.KillOnParentExit = bool.Parse(arg); - grandChild.StartInfo.RedirectStandardOutput = true; - ProcessStartInfo grandChildStartInfo = grandChild.StartInfo; using Process started = WindowsProcessStartArguments.Start(grandChildStartInfo, (WindowsProcessStartArguments args) => @@ -214,7 +211,7 @@ public unsafe void ChildProcess_CanBreakAwayFromJob(bool killOnParentExit) started.WaitForExit(WaitInMS); return started.ExitCode; } - }, killOnParentExit.ToString()); + }); process.StartInfo.KillOnParentExit = killOnParentExit; process.Start(); From 046977fd9944330a14df1647caa60dbd8dff226d Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 30 Jun 2026 16:12:39 +0200 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../tests/ProcessHandlesTests.Windows.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs index 11f4ec728e400b..32576d8b38b67e 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs @@ -185,19 +185,24 @@ public unsafe void ChildProcess_CanBreakAwayFromJob(bool killOnParentExit) startupInfoEx.StartupInfo.hStdError = args.StandardError; startupInfoEx.StartupInfo.dwFlags = Interop.Advapi32.StartupInfoOptions.STARTF_USESTDHANDLES; + int creationFlags = Interop.Kernel32.CREATE_BREAKAWAY_FROM_JOB | Interop.Kernel32.EXTENDED_STARTUPINFO_PRESENT; + if (args.EnvironmentVariables != null) + { + creationFlags |= Interop.Advapi32.StartupInfoOptions.CREATE_UNICODE_ENVIRONMENT; + } + bool retVal = Interop.Kernel32.CreateProcess( null, args.Arguments, ref unused_SecAttrs, ref unused_SecAttrs, bInheritHandles: true, - Interop.Kernel32.CREATE_BREAKAWAY_FROM_JOB | Interop.Kernel32.EXTENDED_STARTUPINFO_PRESENT, + creationFlags, args.EnvironmentVariables, null, &startupInfoEx, &processInfo ); - if (!retVal) { throw new Win32Exception(); @@ -208,8 +213,15 @@ public unsafe void ChildProcess_CanBreakAwayFromJob(bool killOnParentExit) return processInfo.hProcess; }); - started.WaitForExit(WaitInMS); - return started.ExitCode; + try + { + Assert.True(started.WaitForExit(WaitInMS)); + return started.ExitCode; + } + finally + { + started.Kill(); + } } }); From e12a809203fef66646e01001d1f76eef19b45aab Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 30 Jun 2026 16:22:03 +0200 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Adam Sitnik --- .../tests/ProcessHandlesTests.Windows.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs index 32576d8b38b67e..7667511419f41e 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs @@ -191,7 +191,7 @@ public unsafe void ChildProcess_CanBreakAwayFromJob(bool killOnParentExit) creationFlags |= Interop.Advapi32.StartupInfoOptions.CREATE_UNICODE_ENVIRONMENT; } - bool retVal = Interop.Kernel32.CreateProcess( + if (!Interop.Kernel32.CreateProcess( null, args.Arguments, ref unused_SecAttrs, @@ -202,8 +202,7 @@ public unsafe void ChildProcess_CanBreakAwayFromJob(bool killOnParentExit) null, &startupInfoEx, &processInfo - ); - if (!retVal) + )) { throw new Win32Exception(); } @@ -230,7 +229,7 @@ public unsafe void ChildProcess_CanBreakAwayFromJob(bool killOnParentExit) try { - process.WaitForExit(WaitInMS); +Assert.True(process.WaitForExit(WaitInMS)); Assert.Equal(RemoteExecutor.SuccessExitCode, process.ExitCode); } finally From af085463a315e41d6c15e8b236cf1a34e870e295 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 30 Jun 2026 16:22:27 +0200 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Adam Sitnik --- .../tests/ProcessHandlesTests.Windows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs index 7667511419f41e..6054c93e430e32 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.Windows.cs @@ -229,7 +229,7 @@ public unsafe void ChildProcess_CanBreakAwayFromJob(bool killOnParentExit) try { -Assert.True(process.WaitForExit(WaitInMS)); + Assert.True(process.WaitForExit(WaitInMS)); Assert.Equal(RemoteExecutor.SuccessExitCode, process.ExitCode); } finally