-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
Pipe inheritance broken on Windows since 3.13 #133506
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
Comments
You close the file descriptors before waiting on the child, so the child process might not have opened them by the time they're closed. It's a race condition. It wasn't correct on prior versions, but I think you just got lucky that it didn't break before. In 3.13, something (probably startup) is slightly slower, which revealed the race. I would just wait until you've received a message before closing the pipes in the parent. |
There are also a few ways that Python may launch that don't properly handle the CRT's emulation of file descriptors (they aren't real things on Windows, so they don't always match POSIX behaviour perfectly). The That said, if there's actually a bug causing the change on 3.13, we should fix it. If @ZeroIntensity's analysis is correct then maybe it's worth a doc update (if we can choose a good place to put some notes), but ultimately, low-level POSIX emulation on Windows is going to require testing and tweaking to work well. |
No, that is how this should be done. Handles are duplicated, so the parent handles should be closed after child process is spawned. Here is a similar Microsoft example doing exactly the same. https://learn.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output Plus, there is the same behavior if I don't close the child file descriptors in the parent process. |
I need multiples pipes from a 64bit to a 32bit process. So the |
Hm, odd. I don't think it works the same on POSIX. |
It works as expected on Python 3.12 and 3.13 if I pass handles to child process by wrapping it around child_handles = [msvcrt.get_osfhandle(fd) for fd in child_fds]
child_pid = os.spawnl(
os.P_NOWAIT,
sys.executable,
os.path.basename(sys.executable),
"sub.py",
*(str(handle) for handle in child_handles),
) reply_write_handle = int(sys.argv[1])
reply_write_fd = msvcrt.open_osfhandle(reply_write_handle, os.O_NOINHERIT)
request_read_handle = int(sys.argv[2])
request_read_fd = msvcrt.open_osfhandle(
request_read_handle, os.O_RDONLY | os.O_NOINHERIT
) |
@vstinner asked for a use case in #77046 (comment), so I elaborate a little bit: Pipes in combination with pickle is a really elegant and simple pattern for inter process communication. I am aware that the above example could probably be solved with STDOUT / STDIN as pipes and subprocess. However in my specific case I need a third pipe for out of band event messaging. |
Again, the problem as I stated is that you aren't dealing with HANDLEs directly here, but you're dealing with the CRT's file descriptor emulation. So the only directly relevant example would be using the Microsoft C Runtime's APIs exclusively, and not touching any native APIs (like the one you showed). Skim-reading the discussion at #113817 (I don't have time to reabsorb it all right now) it seems we were aware that pipes inside file descriptors were broken when it comes to spawn, and changing the inheritance behaviour really just moves the point of failure (to hopefully not involve a crash). Someone is going to have to dig deeper to find a full resolution (which probably involves reporting bugs to Microsoft and then waiting for new OS releases to include the fix). |
I have just a rough idea what you mean with that. Is there some documentation of these specifics regarding Python API vs. CRT? |
I found an interesting comment in #113817 (comment)
So I guess to correct solution would be to use subprocess with handle_list
|
This is going to work better, yeah. You should be able to open_osfhandle on the other side to get a file descriptor back, but sharing the HANDLE value directly is going to be working with the proper OS APIs. However, do be aware that if this goes via the
Unfortunately not. Our documentation is a specification, so if we explain something in docs, we then have to work hard to preserve it, but the main guarantee Python makes is "provides access to your platform's C runtime". So if we specify what the platform's C runtime has to do, and then the platform changes it, now we're wrong. So we tend not to document a lot of this stuff. (And a lot of the documentation that's written is written by people who don't consider this, and so they do document things either incorrectly, too specifically, or just for a single OS without mentioning that they only meant it for a single OS.) But if you go searching for something like "msvcrt spawn inherit pipes" then you'll find more generic information about the runtime (though in this case I just found the equivalent bug to this one but for Go...). There doesn't seem to be a lot out there about this one, perhaps because nobody writes this kind of code and expects it to be cross-plat? |
Yes, this works with spawn (see #133506 (comment)) and with subprocess. |
This seems to work in my case. Haven't tried a venv. Thanks for your thorough explanation! |
Bug report
Bug description:
Passing an inheritable pipe file descriptor to a child process is somehow broken on Windows since Python 3.13. Opening the file descriptor in the child process crashes with [WinError 6] the handle is invalid.
Minimal example
CPython versions tested on:
3.12, 3.13
Operating systems tested on:
Windows
The text was updated successfully, but these errors were encountered: