-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Implement ProcessStartInfo.InheritHandles for Windows #107836
base: main
Are you sure you want to change the base?
Conversation
Note regarding the
|
1 similar comment
Note regarding the
|
6407dcf
to
dde9ffa
Compare
Debug.Assert(attributeListSize > 0); | ||
Debug.Assert(attributeListSize < 1024); | ||
|
||
byte* newList = stackalloc byte[(int)attributeListSize]; |
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.
FYI: attributeListSize
is 48 on my Windows 11 machine. Should I add a runtime check instead of just asserts here to make sure this not going to exhaust the stack?
dde9ffa
to
3c5c0f4
Compare
Is the intent that the Unix implementation would come separately? I thought the plan there was to do what's called out in #13943 (comment) and explicitly walk through handles > 2 closing each? |
That was my intent: implement the approved Windows-only API and then follow up with the unix implementation. I moved a unit test out of the Windows-specific file to minimize the amount of churn this will cause. |
It wasn't approved as windows only, though, was it? |
The most recent API review I could find was from 2020, which has the setter labeled as Windows only: Jan Kotas said later in the thread:
Maybe I'm misunderstanding which proposal he is referring to (there are many ideas thrown around in that thread), but it seems clear that initially the new API is Windows specific. |
IIRC, my concern was about the perf characteristics of the The idea was to use this API to also allow people to opt-in into a better perf/scalability of launching new processes on Windows: #31556 . Do you plan to implement this perf improvement as part of this PR? If we were to implement this API on non-Windows, how are we going to document when this API helps perf and when it hurts perf? |
I've not thought of the API as being about perf, but rather about correctness, wanting to be able to launch child processes without worrying about what file descriptors the child might accidentally inherit and lead to problems in the parent. If it also helps with a performance issue on Windows around starting many processes, great, and if we can make it fast on newer versions of Linux, great. As long as we have a correct way to implement it on Unix in general, though, and I believe @tmds has suggested that the approach of manually closing each file descriptor > 2 is sound (Tom?), then that would seem to be appropriate. |
I think I see how the lock can be avoided without violating correctness or backwards compatibility. I would switch to using a reader-writer style lock. When |
For reference: #31556 (comment) |
I pushed an update that uses the reader writer lock. I have a benchmark in this repo, which I will duplicate below: Codeusing BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Diagnostics;
BenchmarkRunner.Run<ProcessLaunchBenchmarks>(args: args);
public class ProcessLaunchBenchmarks
{
const string ECHOED_OUTPUT = "hello world";
[Params(1, 16, 256)]
public int NumberOfProceses;
[Benchmark]
public void LaunchStandard()
{
Parallel.For(0, NumberOfProceses, i =>
{
var psi = new ProcessStartInfo("cmd.exe", "/c exit");
using Process p = Process.Start(psi)!;
p.WaitForExit();
});
}
[Benchmark]
public void LaunchWithRedirection()
{
Parallel.For(0, NumberOfProceses, i =>
{
var psi = new ProcessStartInfo("cmd.exe", $"/c echo {ECHOED_OUTPUT}");
psi.RedirectStandardOutput = true;
using Process p = Process.Start(psi)!;
_ = p.StandardOutput.ReadToEnd();
p.WaitForExit();
});
}
} When I ran this on my 16 core machine , you can see that using the reader-writer lock add some overhead when launch a single process repeatedly. When concurrently launching larger number of processes, the reader lock allows process creation to scale better. When the writer side is needed (redirecting output while InheritHandles is true), the reader-writer lock performs a little worse than the regular lock.
I still need to write a benchmark with redirection and |
Some numbers: On my laptop (with the default config) max fd is Since the original issue was created, many problems were solved on Unix by adopting CLOEXEC. |
Ok. |
@tmds, (unrelated to this PR, but) is this something we can use elsewhere in runtime with, e.g., a cached wrapper in some of the places calling |
Looking at the documentation for ReaderWriterLockSlim:
This means as long as there are threads waiting on the writer side of the lock (in this case: launching processes with redirection and InheritHandles = true), threads on the read side (no redirection) of the lock will never acquire the lock. This is a potential starvation issue. Is this an unacceptably bad performance problem and if it is, are there any other reader-writer style syncronization primitives available that try to prevent starvation of the reader side? I know of |
The non-slim |
I updated to using the non-Slim ReaderWriterLock and reran my benchmark. Performance results are similar.
(I'm not sure why the .NET 9 RC 1 numbers are slightly different than before; it's the same machine, but I'm connected with Remote Desktop instead logged in interactively...) |
d557b14
to
a460eff
Compare
a460eff
to
d94dbac
Compare
Since
ShellExecuteEx
andCreateProcessWithLogonW
don't expose a way to disable handles, an exception is thrown whenInheritHandles
is set tofalse
and eitherUseShellExecute
is true orUserName
is set onProcessStartInfo
.Contributes to #13943 (API approval is here in that issue).