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>
|
@adamsitnik Let me know once you believe my feedback is addressed properly, and this is ready for another review |
|
@copilot please invoke the code-review skill and post the analysis/comments as a comment on this PR |
- use SafeHandles instead of raw handles - use DangerousAddRef and DangerousRelease for handles we don't control - simplify comments - call SetConsoleCtrlHandler in the test process to avoid weird pop up window - make ProcessId public - add tests for KillOnParentExit
...ries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs
Show resolved
Hide resolved
| // - powershell is not available on Nano. We can't always use it. | ||
| // - ping seems to be a workaround, but it's simple and work everywhere. The arguments are set to make it sleep for approximately 10 seconds. | ||
| private static ProcessStartOptions CreateTenSecondSleep() => OperatingSystem.IsWindows() | ||
| ? new("ping") { Arguments = { "127.0.0.1", "-n", "11" } } |
There was a problem hiding this comment.
0xc0000142 is STATUS_DLL_INIT_FAILED.
How does missing ReEnableCtrlHandlerIfNeeded lead to this error?
I would expect that missing ReEnableCtrlHandlerIfNeeded will make it impossible to send the signal to the process. I do not see how it can lead to STATUS_DLL_INIT_FAILED.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.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
...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/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs
Show resolved
Hide resolved
| } | ||
| finally | ||
| { | ||
| nullHandle?.Dispose(); |
There was a problem hiding this comment.
I wonder if it wouldn't make sense to dispose all handles in this finally block that are intended for the child process?
I think it helps on the caller side since usually you won't have a block that sits around just the Start call, so with a using these handles will be open longer than they should be.
@adamsitnik thoughts?
There was a problem hiding this comment.
@tmds I really like this idea, it would simplify the implementation and make it more reliable (#124375 (comment)). At the same time, it would help us to avoid confusion like in my prototype, where only pipes were getting disposed.
And if somebody does not like it, they can pass a SafeFileHandle with ownsHandle: false
@jkotas @stephentoub thoughts?
There was a problem hiding this comment.
This design would mean the one has to create a fresh set of SafeHandle instances for each process start. For example, this would not work:
var stdout = OpenStandardOutputHandle();
SafeProcessHandle.Start(... , output: stdout);
SafeProcessHandle.Start(... , output: stdout);And one would have to do this instead:
SafeProcessHandle.Start(... , output: OpenStandardOutputHandle());
SafeProcessHandle.Start(... , output: OpenStandardOutputHandle());
Is my understanding correct?
And if somebody does not like it, they can pass a SafeFileHandle with ownsHandle: false
ownsHandle: false would still have the problem with the handle being marked as inheritable by the process start.
There was a problem hiding this comment.
Right, for some use-cases you want this and for others you don't.
The question also applies to the InheritedHandles.
There was a problem hiding this comment.
It might make sense add a bool leaveOpen = false similar to other APIs.
There was a problem hiding this comment.
It might make sense add a
bool leaveOpen = falsesimilar to other APIs.
But this would lead to another question: should we allow for setting leaveOpen per handle?
Since we provide InheritedHandles for completeness and I expect it to be a very niche API (used by users who use anonymous pipes for communicating with their child processes), I think it's OK to document it as required to be disposed by the users.
There was a problem hiding this comment.
If we're considering an API, I'd consider a bool leaveOpen = false arg to Start and ProcessStartOptions.CloseInheritedHandlesOnStart = true, so that all handles are disposed by default and a user can opt-out and do things manually.
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
- improve test coverage - make InheritedHandles and non-inheritable after process is started so other processes don't inherit these handles - use RemoteExecutor to test the new APIs - stop using WaitForSingleObject
...ries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs
Outdated
Show resolved
Hide resolved
| // - powershell is not available on Nano. We can't always use it. | ||
| // - ping seems to be a workaround, but it's simple and work everywhere. The arguments are set to make it sleep for approximately 10 seconds. | ||
| private static ProcessStartOptions CreateTenSecondSleep() => OperatingSystem.IsWindows() | ||
| ? new("ping") { Arguments = { "127.0.0.1", "-n", "11" } } |
There was a problem hiding this comment.
My best guess is some race
It was 100% repro. It is very unlikely to be a race.
This error typically means that the process environment is badly corrupted. We should try to understand what lead to it. There may be a real problem that only repros with some processes, and RemoteExecutor is lucky to not repro it.
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Description
Implements the approved API surface for
SafeProcessHandle(#123380): process lifecycle management viaStart,Open,Kill,Signal,SignalProcessGroup, andWaitForExitoverloads. Windows implementation complete; Unix stubs throwNotImplementedException(separate PR).Public API
Open(int processId)— opens existing process handleStart(ProcessStartOptions, SafeFileHandle?, SafeFileHandle?, SafeFileHandle?)— create process with explicit stdio handlesKill()— terminate processSignal(PosixSignal)/SignalProcessGroup(PosixSignal)— send signal; accepts raw signal numbers likePosixSignalRegistration.CreateWaitForExit(),TryWaitForExit(TimeSpan, out ProcessExitStatus?),WaitForExitOrKillOnTimeout(TimeSpan)— synchronous waitWaitForExitAsync(CancellationToken),WaitForExitOrKillOnCancellationAsync(CancellationToken)— async waitInternal for now (pending follow-up):
ProcessId,StartSuspended,Resume,KillProcessGroup.Windows implementation
STARTUPINFOEX+PROC_THREAD_ATTRIBUTE_HANDLE_LISTfor explicit handle inheritanceKillOnParentExitand process group termination (best-effort emulation of Unix process groups)DangerousAddRef/DangerousReleaseonInheritedHandlesinPrepareHandleAllowListand onthisinKillCoreto prevent use-after-free races with concurrentDisposeInterlocked.Exchangefor_threadHandleand_processGroupJobHandleinReleaseHandle;Volatile.Readfor_processGroupJobHandleaccess elsewhereWaitForSingleObject(SafeProcessHandle, ...)directly — no handle duplication viaProcessWaitHandleProcessWaitHandleforRegisterWaitForSingleObjectNativeMemory.Allocwith two-arg overflow-safe version; typed pointers (IntPtr*,void*)finally; error check immediately afterCreateProcessArchitecture —
ProcessUtilsas shared dependencyProcessUtils.csholdss_processStartLock(ReaderWriterLockSlim) — avoidsProcess↔SafeProcessHandledependency cycleProcessUtils.Windows.csholdsBuildArgs(string, IList<string>?)andGetEnvironmentVariablesBlockProcess.Windows.csacquires write lock;SafeProcessHandle.Windows.csacquires read lockInterop additions
Interop.JobObjects.cs,Interop.ProcThreadAttributeList.cs,Interop.ConsoleCtrl.cs(consolidated),Interop.HandleInformation.cs(extended),Interop.ResumeThread_IntPtr.cs,Interop.WaitForSingleObject_SafeProcessHandle.csTests
SafeProcessHandleTests.cs— start/kill/wait/timeout/signal tests with Nano Server support (pingfallback); signal tests as[Theory]with[InlineData]💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.