-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Add CreateNewProcessGroup to ProcessStartInfo #117105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for creating a new process group via a CreateNewProcessGroup
property on ProcessStartInfo
. It extends the Windows implementation, stubs the API on Unix with proper exceptions, and includes interop constants plus cross-platform tests.
- Introduces a new
CreateNewProcessGroup
property inProcessStartInfo
(Windows) and a stub on Unix. - Updates the Windows
StartWithCreateProcess
path to include theCREATE_NEW_PROCESS_GROUP
flag and adds the corresponding interop constant. - Adds tests to verify the property behavior on both platforms and a signal‐handling scenario using remote executor.
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
src/libraries/System.Diagnostics.Process/tests/*.csproj | Linked additional interop files: BOOL, SetConsoleCtrlHandler, Unix Libraries, and PosixSignal helpers |
src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs | Added TestCreateNewProcessGroup_HandlerReceivesExpectedSignal to exercise signal dispatch |
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs | Implemented SendSignal and ReEnableCtrlCHandlerIfNeeded for Windows |
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs | Implemented SendSignal stub and no-op ReEnableCtrlCHandlerIfNeeded for Unix |
src/libraries/System.Diagnostics.Process/tests/ProcessStartInfoTests.cs | Added unit tests for CreateNewProcessGroup on Windows and Unix |
src/libraries/System.Diagnostics.Process/tests/Interop.cs | Added GenerateConsoleCtrlEvent DllImport |
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartInfo.Windows.cs | Added CreateNewProcessGroup property |
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartInfo.Unix.cs | Added stub CreateNewProcessGroup with PlatformNotSupportedException |
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs | ORs CREATE_NEW_PROCESS_GROUP into the creation flags |
src/libraries/Common/src/Interop/Windows/Advapi32/Interop.ProcessOptions.cs | Added CREATE_NEW_PROCESS_GROUP constant |
|
||
Console.WriteLine(PosixSignalRegistrationCreatedMessage); | ||
|
||
while (!receivedSignal) ; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The busy-wait loop spins the CPU continuously. Consider using a synchronization primitive (e.g. ManualResetEventSlim
) or at least inserting Thread.Sleep(1)
(or Thread.Yield()
) inside the loop to reduce CPU usage.
while (!receivedSignal) ; | |
while (!receivedSignal) | |
{ | |
Thread.Sleep(1); | |
} |
Copilot uses AI. Check for mistakes.
@@ -52,5 +52,12 @@ public SecureString? Password | |||
get { throw new PlatformNotSupportedException(SR.Format(SR.ProcessStartSingleFeatureNotSupported, nameof(Password))); } | |||
set { throw new PlatformNotSupportedException(SR.Format(SR.ProcessStartSingleFeatureNotSupported, nameof(Password))); } | |||
} | |||
|
|||
[SupportedOSPlatform("windows")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we throwing PNSE now on Unix as part of reserving the possibility of implementing this in the future? Or we think this is permanently Windows only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is possible to implement this in Unix, however, no one has shown any needs for it, so the throw is a good safeguard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This API would not map well to the Unix concept of process group. I do not expect this API to be implemented on Unix ever.
[SupportedOSPlatform("windows")] | ||
public bool CreateNewProcessGroup | ||
{ | ||
get { throw new PlatformNotSupportedException(SR.Format(SR.ProcessStartSingleFeatureNotSupported, nameof(CreateNewProcessGroup))); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get doesn't need to throw, right? It could just always return false?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was following the existing pattern but returning false does feel better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returning false does feel better.
To me, it feels better to be consistent with the established pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why we have that as a pattern in this particular class. Elsewhere we have the getters that just return the value do so even when the setters throw PNSE:
runtime/src/libraries/System.Console/src/System/ConsolePal.Unix.cs
Lines 297 to 329 in b509ab1
public static int BufferWidth | |
{ | |
get { return WindowWidth; } | |
set { throw new PlatformNotSupportedException(); } | |
} | |
public static int BufferHeight | |
{ | |
get { return WindowHeight; } | |
set { throw new PlatformNotSupportedException(); } | |
} | |
public static int LargestWindowWidth | |
{ | |
get { return WindowWidth; } | |
} | |
public static int LargestWindowHeight | |
{ | |
get { return WindowHeight; } | |
} | |
public static int WindowLeft | |
{ | |
get { return 0; } | |
set { throw new PlatformNotSupportedException(); } | |
} | |
public static int WindowTop | |
{ | |
get { return 0; } | |
set { throw new PlatformNotSupportedException(); } | |
} |
Even elsewhere in Process we'll make getters work even when we throw PNSE from setters, e.g.
runtime/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.Linux.cs
Lines 17 to 30 in b509ab1
private ThreadPriorityLevel PriorityLevelCore | |
{ | |
// This mapping is relatively arbitrary. 0 is normal based on the man page, | |
// and the other values above and below are simply distributed evenly. | |
get | |
{ | |
Interop.procfs.ParsedStat stat = GetStat(); | |
return Interop.Sys.GetThreadPriorityFromNiceValue((int)stat.nice); | |
} | |
set | |
{ | |
throw new PlatformNotSupportedException(); // We can find no API to set this on Linux | |
} | |
} |
If we can minimize friction by having getters work, I think we should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ProcessStartInfo.CreateNewProcessGroup and other similar ProcessStartInfo properties are typically only going be set. They are unlikely to be read by the app. If they are read by the app, we probably want the platform analyzer flag them since it is not expected to use these properties in platform neutral code. Throwing for both getter and setter makes the story coherent.
Console.BufferWidth/Height
are likely to be read by platform-neutral code. It makes sense that we made the getter work where possible.
There are other niche properties on Console
, like Console.TreatControlCAsInput
, where both getter and setter throws on some platforms.
I think the pattern that I see is that we make the getter return dummy value instead of throwing only when there is a plausible scenario for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These wouldn't be dummy values. They're the default values the properties have, regardless of platform. For a property that defaults to false or null or 0 (rather than it needing to read some system state) I do not see what it helps having the getter throw PNSE. What is the benefit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think throwing vs. returning default value makes a difference for user experience for niche properties like this one. I think it just about our design guideline for the x-plat behavior of these properties. We want to be consistent. I believe that our preference is to throw currently.
If we decide that our preference is to return default value whenever possible instead, I do not have a problem with it but I do not see the value. If we do that, we may want to go over the property getters that are throwing PlatformNotSupportedException
currently and change them to return default value where possible instead for consistency (and update their platform support annotations to match).
// See https://learn.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw#remarks: | ||
// When a process is created with CREATE_NEW_PROCESS_GROUP specified, an implicit call to SetConsoleCtrlHandler(NULL,TRUE) | ||
// is made on behalf of the new process; this means that the new process has CTRL+C disabled. | ||
private static unsafe void ReEnableCtrlCHandlerIfNeeded(PosixSignal signal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the purpose of this additional feature is to be able to send ctrl-C to a process, and if ctrl-C ends up being disabled by default in that process, isn't that going to be a problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a pit of failure in Windows, IMO, especially for apps that don't terminate with Ctrl+Break, like ping.exe.
The workaround is to use CreateProcess with a helper app that re-enables Ctrl+C and calls CreateProcess to the app you want to originally start, or if you have control over your target app, reenable Ctrl+C directly there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the parent and child processes have to cooperate to make the scheme work on WIndows. It is one of the differences between Windows and Unix designs for process groups.
Will fix #44944, provided that API review approves it.