Description
std Proposal and Context
This blog post explains how to do it https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873
The basic principle is to provide the CreateProcess method with a list of all handles: They must be regular ones and they must be set to inheritable.
In comparison Unixes like Linux leak the inheritable descriptors (those not marked with CLOEXEC), if another thread spawns processes at the same time. Another problem exists, if one does use CLOEXEC and one/external_code does not use exec*.
This would improve perf of multi-threaded code depending on no leaks, since there is no explicit mutex necessary.
Rust does not use this and uses a full process mutex rust-lang/rust#38227 to this day and has several other open handle-related problems related to it: rust-lang/rust#54760 rust-lang/rust#70719.
Note that trying out multiple executions along PATH and PATHEXT can increase the potential leaking time on Windows.
I started this, but got stuck on UpdateProcThreadAttribute
failing with a bogous error message in https://github.com/matu3ba/zig/tree/extra_streams4.
The idea is to keep a buffer of 3 handles for stdin, stdout and stderr, but otherwise let the user provide one. The main caveats are that Windows may change the underlying attribute structure, as it requires dynamic probing of the attribute list size and usage of the setup function:
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
fSuccess = cHandlesToInherit < 0xFFFFFFFF / sizeof(HANDLE) &&
lpStartupInfo->cb == sizeof(*lpStartupInfo);
if (!fSuccess) {
SetLastError(ERROR_INVALID_PARAMETER);
}
if (fSuccess) {
fSuccess = InitializeProcThreadAttributeList(NULL, 1, 0, &size) ||
GetLastError() == ERROR_INSUFFICIENT_BUFFER;
}
if (fSuccess) {
lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>
(HeapAlloc(GetProcessHeap(), 0, size));
fSuccess = lpAttributeList != NULL;
}
if (fSuccess) {
fSuccess = InitializeProcThreadAttributeList(lpAttributeList,
1, 0, &size);
}
if (fSuccess) {
fInitialized = TRUE;
fSuccess = UpdateProcThreadAttribute(lpAttributeList,
0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
rgHandlesToInherit,
cHandlesToInherit * sizeof(HANDLE), NULL, NULL);
}
Expected Behavior
Default to no possible leaks on Windows, even if the user forgets to disable inheritance on handles.
UPDATE: Due to backwards incompatibility, since a user can not "overwrite enabling file handle inheritance as list for only this one to be spawned process" an opt-in sounds better.
Possible drawback: External code may use the non-explicit code, which can leak the handles if they are set to inheritable.
Related footgun: Handles must be set to inheritable, even for the explicit list method, to be inherited.