Add Process constructor accepting SafeProcessHandle and optional stream handles#128491
Add Process constructor accepting SafeProcessHandle and optional stream handles#128491Copilot wants to merge 7 commits into
Conversation
…ream handles Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3a2f55b9-2cc6-46ba-87af-1dc53029c488 Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…unused usings Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3a2f55b9-2cc6-46ba-87af-1dc53029c488 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
|
Tagging subscribers to this area: @dotnet/area-system-diagnostics-process |
…ANDLE_VALUE, extract CreateProcessWithHandles helper Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/12cdb475-8f8c-4309-b5d7-d0b02c675048 Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Done in a53a1fc. All three items addressed:
|
… Windows Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/b6feaf9e-a3a5-4056-a01a-9aa9761be8be Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/1ed7a3e4-eda7-4547-a02f-cbb94f8dccac 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
…dd IsAsync test - Make ProcessStartInfo a mandatory parameter (moved to second position) with ArgumentNullException validation to prevent StartInfo.get from throwing when the process is associated. - Restore GetSafeFileHandleId helper that was incorrectly removed (it is used in ProcessHandlesTests.cs). - Document handle ownership transfer in XML docs for the constructor. - Add tests verifying ArgumentException is thrown when non-async handles are passed for standardOutput/standardError on Windows. - Update ref assembly to match new signature. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3b168351-c6d4-4437-8a79-e949d29ff057 Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
All feedback addressed in b7cc712:
|
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot address my feedback
…dle.IsInvalid - Merged two similar IsAsync validation tests into a single [Theory] - Removed trailing empty line before closing brace - Added processHandle.IsInvalid validation in constructor Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ad6e82f0-2e52-4b04-b10f-b3add859ad18 Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
| using (errorReadHandle) | ||
| using (errorWriteHandle) | ||
| { | ||
| // Enable inheritance on the write ends so the child process can use them. |
There was a problem hiding this comment.
This is robust only if you control all other process starts that may be done by the current process.
If I am reading this correctly, this will create inheritable handles in the main test process without synchronizing with other process starts and so the handles may get accidently inherited by other processes. Is this test going to be flaky because of that?
Also, do the docs need to mention all gotchas about inherited handles and how the user code is expected to deal with them? I am not sure whether this API is the best way to address the end-to-end scenario once you take all the gotchas into account.
There was a problem hiding this comment.
This is robust only if you control all other process starts that may be done by the current process.
I agree, but this is true for all ways of starting processes on Windows (the standard handles always need to be inheritable).
If I am reading this correctly, this will create inheritable handles in the main test process without synchronizing with other process starts and so the handles may get accidently inherited by other processes.
Correct.
Is this test going to be flaky because of that?
All process tests are executed sequentially, so not for now.
Also, do the docs need to mention all gotchas about inherited handles and how the user code is expected to deal with them?
They don't for now (but this can be ofc fixed).
I am not sure whether this API is the best way to address the end-to-end scenario once you take all the gotchas into account.
The alternative is to let the users pass the windows process attributes (and creation flags?) via ProcessStartInfo, something that we have discussed offline a while ago that would be passed to UpdateProcThreadAttribute:
public List<(int attribute, GCHandle value, nint byteSize)> ProcessAttributes { get; set; }But it has one major limitation: it would support only the CreateProcess sys-call. So for example if the user wanted to use CreateProcessAsUser to implement #128378, this would not work.
There was a problem hiding this comment.
alternative
I think there are more alternatives. For example, we can allow user to provide a callback that receives some of the arguments prepared for the Win32 API invocation, the user provided callback combines these arguments with their custom options and invokes the Win32 APIs of their choice.
There was a problem hiding this comment.
For example, we can allow user to provide a callback that receives some of the arguments prepared for the Win32 API invocation
There are few simple arguments that such callback would need to accept that would be easy to implement:
- pointer to the command line string (
char*) - pointer to new environment block (
char*) - pointer to current directory name (
char*) - handle inheritance flag (
bool) - creation flags (
int)
But advanced usages like the use case described by the customer in #71595 (comment)
I'm currently using JobObjects to limit CPU time and RAM. But I'd also like to restrict file and network access.
So, I need to use CreateRestrictedToken (or simular) API with Process capturing capabilities.
would also require us to expose STARTUPINFO and STARTUPINFOEX.
Moreover, really advanced scenarios like specifying Jobs would be still very tricky to get right together with all our existing APIs. For example, to provide Jobs, we need to specify the count up-front to allocate the right size of a buffer:
The same is true for attribute count:
And some of the Windows sys-calls used for spawning processes don't support STARTUPINFOEX with lpAttributeList (CreateProcessWithTokenW docs mention STARTUPINFOEX, but they don't mention that EXTENDED_STARTUPINFO_PRESENT is not supported anyway.). So in certain situations users would need to ignore some of the input we provide (like the attribute/job list) and start a suspended process, then add the jobs, resume the process and all of that like we do here
@jkotas from my perspective asking the users to deal with handle inheritance is lesser evil compared to exposing all the Win32 contracts and trying to provide a callback that would support all scenarios.
Also the proposed ctor could work on Unix as well. As users could just somehow spawn the process, use SafeProcessHandle.Open and give us the SPH and the pipe handles.
There was a problem hiding this comment.
would also require us to expose STARTUPINFO and STARTUPINFOEX
I do not think we need to expose these structures. We can prepare the stdin/stdout/stderr handles, command line, environment, take the process create lock, and leave everything else to be provided by the user. They can supply their own definition of the PInvoke and all structures that the PInvoke needs. For example, if they want to start a process with custom job settings, they can do it all on their own.
Also the proposed ctor could work on Unix as well.
I would like to see what a "done" state looks like for the long tail of process APIs features. I do not think we want to be on the feature creep path (adding an API for every niche feature somebody asks for) that we are on currently. I do not have a fundamental problem with a few more APIs if we think that the use case is common enough.
Adds a new public
Processconstructor that creates instances from user-provided safe handles, enabling advanced interop scenarios where users callCreateProcess(or equivalent) directly but still wantProcessclass features like async stream reading andWaitForExit.Description
API Addition
On Windows,
standardOutputandstandardErrorhandles must be opened for asynchronous I/O (IsAsync == true); anArgumentExceptionis thrown otherwise. TheprocessHandlemust be valid (IsInvalid == false); anArgumentExceptionis thrown otherwise. The caller transfers ownership of the standard stream handles to theProcessinstance — they will be disposed when theProcessis disposed.Changes
ref/System.Diagnostics.Process.cs): New constructor signatureProcess.cs): Constructor wires up handle, process ID, and wraps optional file handles intoStreamWriter/StreamReaderusing existingOpenStream+ encoding logic. Validates thatprocessHandleis valid, thatstandardOutput/standardErrorhandles are async on Windows, and thatprocessHandle/startInfoare non-null.ProcessStartInfois mandatory (withArgumentNullException) to preventStartInfo.getfrom throwing on an associated process.ProcessHandlesTests.Windows.cs):CreateProcessWithHandlesstatic helper reused by bothRunWithHandlesand the new testINVALID_HANDLE_VALUEconstant to replace magic numbersProcessStartedWithAnonymousPipeHandles_CanCaptureOutputtest: creates async anonymous pipes, enables inheritance, spawns child process, wraps read handles in newProcessctor, usesReadAllText()to assert captured stdout/stderrProcessConstructor_NonAsyncHandle_ThrowsArgumentException[Theory]test for IsAsync validation of both stdout and stderrStrings.resx): AddedArgument_HandleNotAsyncerror messageUsage Example