Add new process management APIs to SafeProcessHandle#124375
Add new process management APIs to SafeProcessHandle#124375
Conversation
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…, Theory for utils Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…h for sh Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…syntax Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
… remove dead code Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…names Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…s, and Windows implementation Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.Windows.cs
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs
Show resolved
Hide resolved
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot please address my feedback
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs
Outdated
Show resolved
Hide resolved
…Fact; add Nano Server support to CreateTenSecondSleep Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot please apply my suggestions to fix the tests that are failing only on Windows Nano
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.Windows.cs
Outdated
Show resolved
Hide resolved
|
When |
...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
| using Interop.Kernel32.ProcessWaitHandle processWaitHandle = new(this); | ||
| if (!processWaitHandle.WaitOne(milliseconds)) | ||
| { | ||
| wasKilledOnTimeout = KillCore(throwOnError: false, entireProcessGroup: _processGroupJobHandle != IntPtr.Zero); |
There was a problem hiding this comment.
Do we need a test for the bug that was fixed here in the commit 776b250 ?
There was a problem hiding this comment.
@copilot address this feedback, provide a test that spawns a process that spawns another process and ensure both run for a long time and get killed on timeout.
| { | ||
| public partial class SafeProcessHandleTests | ||
| { | ||
| [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] |
There was a problem hiding this comment.
Why is this disabled on Nano?
The equivalent Process test does not appear to be disabled on Nano:
runtime/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
Lines 108 to 110 in d7362ec
| } | ||
|
|
||
| [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] | ||
| public void Signal_SIGQUIT_TerminatesProcessInNewProcessGroup() |
There was a problem hiding this comment.
This and the previous tests only differ by PosixSignal number. They should use theory.
Also, I expect you are going to have the same test - just for more signals - for Unix too. Should it be in OS-neutral file?
| fixed (char* applicationNamePtr = &applicationName.GetPinnableReference()) | ||
| fixed (char* commandLinePtr = &commandLine.GetPinnableReference()) |
There was a problem hiding this comment.
| fixed (char* applicationNamePtr = &applicationName.GetPinnableReference()) | |
| fixed (char* commandLinePtr = &commandLine.GetPinnableReference()) | |
| fixed (char* applicationNamePtr = applicationName) | |
| fixed (char* commandLinePtr = commandLine) |
Nit: I do not think you need an explicit GetPinnableReference reference call.
| bool retVal = Interop.Kernel32.CreateProcess( | ||
| applicationNamePtr, | ||
| commandLinePtr, | ||
| ref unused_SecAttrs, | ||
| ref unused_SecAttrs, | ||
| true, | ||
| creationFlags, | ||
| environmentBlockPtr, | ||
| workingDirectory, | ||
| ref startupInfoEx, | ||
| ref processInfo | ||
| ); | ||
| if (!retVal) | ||
| errorCode = Marshal.GetLastPInvokeError(); |
There was a problem hiding this comment.
| bool retVal = Interop.Kernel32.CreateProcess( | |
| applicationNamePtr, | |
| commandLinePtr, | |
| ref unused_SecAttrs, | |
| ref unused_SecAttrs, | |
| true, | |
| creationFlags, | |
| environmentBlockPtr, | |
| workingDirectory, | |
| ref startupInfoEx, | |
| ref processInfo | |
| ); | |
| if (!retVal) | |
| errorCode = Marshal.GetLastPInvokeError(); | |
| if (!Interop.Kernel32.CreateProcess( | |
| applicationNamePtr, | |
| commandLinePtr, | |
| ref unused_SecAttrs, | |
| ref unused_SecAttrs, | |
| true, | |
| creationFlags, | |
| environmentBlockPtr, | |
| workingDirectory, | |
| ref startupInfoEx, | |
| ref processInfo)) | |
| { | |
| errorCode = Marshal.GetLastPInvokeError(); | |
| } |
Nit: Coding conventions for { }
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot please address the feedback
| fixed (char* applicationNamePtr = &applicationName.GetPinnableReference()) | ||
| fixed (char* commandLinePtr = &commandLine.GetPinnableReference()) |
| bool retVal = Interop.Kernel32.CreateProcess( | ||
| applicationNamePtr, | ||
| commandLinePtr, | ||
| ref unused_SecAttrs, | ||
| ref unused_SecAttrs, | ||
| true, | ||
| creationFlags, | ||
| environmentBlockPtr, | ||
| workingDirectory, | ||
| ref startupInfoEx, | ||
| ref processInfo | ||
| ); | ||
| if (!retVal) | ||
| errorCode = Marshal.GetLastPInvokeError(); |
| using Interop.Kernel32.ProcessWaitHandle processWaitHandle = new(this); | ||
| if (!processWaitHandle.WaitOne(milliseconds)) | ||
| { | ||
| wasKilledOnTimeout = KillCore(throwOnError: false, entireProcessGroup: _processGroupJobHandle != IntPtr.Zero); |
There was a problem hiding this comment.
@copilot address this feedback, provide a test that spawns a process that spawns another process and ensure both run for a long time and get killed on timeout.
| { | ||
| public partial class SafeProcessHandleTests | ||
| { | ||
| [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] |
| } | ||
|
|
||
| [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] | ||
| public void Signal_SIGQUIT_TerminatesProcessInNewProcessGroup() |
It would be a surprise to me. This is the implementation of the condition: https://github.com/dotnet/arcade/blob/55c97a45af8fd75bc87e9e4b4f61aef3fead254d/src/Microsoft.DotNet.RemoteExecutor/src/RemoteExecutor.cs#L110-L118 . I do not see anything that would disable it on Nano. There are subtle behavior difference between full Windows and Nano in how to signals behave (see #115206 (comment)), but as far as I know the signals generally work just fine on Nano. If I had to guess, it may be a problem with the process group emulator via Windows jobs. |
The other test (CreateNewProcessGroup_CanBeSetToTrue) uses process group and works fine on Nano. The emulation is very simple, it's just one Job created and assigned to process. When I tried enabling Windows signals on Nano, I was getting And the test that you have referenced is silently swallowing this error: |
…PinnableReference, fix CreateProcess braces Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

Description
Implements the approved API surface for
SafeProcessHandle(#123380): process lifecycle management viaStart,Open,Kill,Signal,SignalProcessGroup, andWaitForExitoverloads. Windows implementation is complete; Unix stubs throwNotImplementedException(separate PR).SafeProcessHandle new public API
Open(int processId)— opens existing process handleStart(ProcessStartOptions, SafeFileHandle?, SafeFileHandle?, SafeFileHandle?)— process creation with explicit stdio handlesKill()/Signal(PosixSignal)/SignalProcessGroup(PosixSignal)— termination and signalingWaitForExit(),TryWaitForExit(TimeSpan, out ProcessExitStatus?),WaitForExitOrKillOnTimeout(TimeSpan)— synchronous waitWaitForExitAsync(CancellationToken),WaitForExitOrKillOnCancellationAsync(CancellationToken)— async waitInternal for now (pending follow-up PRs):
ProcessId,StartSuspended,Resume,KillProcessGroup.Windows implementation
STARTUPINFOEX+PROC_THREAD_ATTRIBUTE_HANDLE_LISTfor explicit handle inheritanceKillOnParentExitand process group terminationfinally;NativeMemory.Allocwith overflow-safe two-arg formReaderWriterLockSlim(ProcessUtils.s_processStartLock) prevents accidental handle inheritance betweenProcess.Start(write lock) andSafeProcessHandle.Start(read lock)Interlocked.Exchangeon_threadHandleprevents double-resume and use-after-free inReleaseHandleArchitecture —
ProcessUtilsas shared dependencyProcessUtils.csholdss_processStartLockto avoidProcess↔SafeProcessHandledependency cyclesProcessUtils.Windows.csholdsBuildArgs(string, IList<string>?)andGetEnvironmentVariablesBlockProcessStartOptionsexposesHasArgumentsBeenAccessed/HasEnvironmentBeenAccessedto avoid allocationsInterop additions
Interop.JobObjects.cs,Interop.ProcThreadAttributeList.cs,Interop.ConsoleCtrl.cs(consolidated),Interop.HandleInformation.cs(extended),Interop.ResumeThread_IntPtr.csTests
SafeProcessHandleTests.cs— start/kill/wait/timeout tests with Nano Server support (pingfallback)SafeProcessHandleTests.Windows.cs— signal tests as[Theory]with[InlineData]💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.