GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-77046: Pass the _O_NOINHERIT flag to _open_osfhandle() calls by vstinner · Pull Request #113817 · python/cpython Conversation
|
|
PR based on the idea of the PR #13739 but rewritten from scratch on the main branch. The main difference with PR #13739 is that I wrote an unit test ;-) It's non trivial to test this fix manually since UCRT has an undefined behavior: #77046 (comment) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
The test is also run on Unix: IMO it doesn't hurt to run more functional tests on Unix systems ;-) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Oh, test_pipe_spawnl() hangs and is killed after a timeout of 10 minutes :-( It's a debug build, test.pythoninfo says:
The test pass when I built Python locally on my Windows VM. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Test looks good, I think we should consider passing in the flag at the calls, rather than always forcing it on (e.g. what if we had made the handle inheritable?)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
It's probably raising a CRT assertion and blocking. Maybe you can use |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Aha. Let me retry using SuppressCrashReport. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| raise | ||
| else: | ||
| raise Exception("get_osfhandle() must fail") | ||
|
|
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
With else it would be clearer that the following code is not a fallback, but an alternative version.
Oh, and because almost all code is different, you can use different scripts, depending on the OS.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
With else it would be clearer that the following code is not a fallback
Sorry, else on which line? Would you mind to elaborate?
The code calls msvcrt.get_osfhandle() and os.dup() on Windows on purpose. I explained it in a comment a few lines before.
Oh, and because almost all code is different, you can use different scripts, depending on the OS.
I prefer to have a single code base on all platforms, since most of the code is in common. Only the code using msvcrt is optional. Even on Windows, msvcrt may not be available on Python implementations other than CPython.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see now that there is a possibility for the code above to not raise an exception. So the code below this line can be executed on Windows.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
The code makes sure that msvcrt.get_osfhandle() and os.dup() both raise an exception on the "not inherited" file descriptor.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@zooba: I updated my PR to pass _O_NOINHERIT flag to _Py_get_osfhandle() callers, instead modifying _Py_get_osfhandle(). |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I wish Python 3.13 would drop primary support for inheritable file descriptors on Windows, for the sake of reliably correct behavior. The use of inheritable file descriptors is currently problematic in Python on Windows because the C runtime doesn't provide a POSIX |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Isn't part of the contract that the FDs have the same fileno in the new process, though? I don't think we can implement that without making our own file descriptor implementation (which I have considered in the past, tbh, but I believe people use them in embeddings and so they really need to be shared with UCRT). The best we can really do is use platform-specific code in higher level libraries (multiprocessing, etc.) and just say that file descriptors and spawn are inherently not portable to Windows. Meanwhile, we can make our approximation work well enough for sensible/likely scenarios so that Linux code ported to Windows by someone else has a chance of working without needing the original devs to rewrite it all. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
I don't think so. Python should create non inheritable file descriptors (PEP 446), and so they should not be inherited by os.spawnl(). Windows should not behave differently than Unix on that aspect. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Note that |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Windows C runtime's
I was referring to the text that I quoted immediately above my statement. Regardless of where the FD comes from or how it became inheritable, if it is inheritable, then a |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Code written for Linux will use One scenario that works is a two-step process that few would ever think to use. Create a new file descriptor via |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
UCRT Extract of // Copy the _osfile information, with the FNOINHERIT bit cleared: _osfile(target_fh) = _osfile(source_fh) & ~FNOINHERIT; _textmode(target_fh) = _textmode(source_fh); _tm_unicode(target_fh) = _tm_unicode(source_fh); As I wrote previously, on Windows, I suggest to inherit handles and then create (new) file descriptors for them in the child process. Maybe in Python 3.12, it's possible to inherit some file descriptors on Windows using os.spawnl(), but for me, it's more an accident than a deliberate choice, and the code is not portable. Another common portable solution is to use a temporary named file rather than a pipe and pass a filename. The disadvantage is that the parent must make sure that the temporary file is deleted. Python libregrtest has 3 ways to pass JSON from a worker process to its parent:
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@zooba @eryksun: We discussed many aspects of inheriting file descriptors on Windows, but are you ok with this specific change? @serhiy-storchaka: I added comments in the test to clarify that the 2 function calls are expected to fail. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Well, this change fix a crash affecting Python 3.12 when os.pipe() file descriptors are inherited, whereas they are supposed to be non inheritable: #77046 (comment) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Clearing the _NO_INHERIT flag means that the file descriptor is inheritable. I think you misread (or got caught out by the double negative). |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
That's correct :-D |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
It's already got my approval on it :)
Really the crash would only be caused by a programming error, where the programmer has incorrectly assumed that the pipe descriptors are inheritable and passed them into a subprocess. Normally, they wouldn't assume it, and wouldn't ever try to access them and so would not crash. And actually, now that I think through that scenario, we might be breaking people who assume that |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Ok, I merged my change. I preferred to make sure that we are on the same page.
I'm not sure what you are referring to. With this change, os.set_inheritable() works as expected on the FDs created by os.pipe(): Note: os.pipe() doesn't call set_inheritable() on Windows, only on Unix. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
So previously, you could do this, and a, b = os.pipe() os.set_inheritable(a, True) os.spawn(<code that uses 'a'>) However, because |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
As I discussed above, Until UCRT implements POSIX It's all such a mess that I go back to my original suggestion. Remove support for inheritable file descriptors from the primary |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
I'm not so concerned about this scenario, because it requires someone to explicitly make it not inheritable and then explicitly try to inherit it in a child process. That's obviously a coding mistake, and the bad behaviour probably isn't much different than what it would be if we correctly handled things (and likely it's better because there's no risk of the inherited-corrupt file descriptor being replaced by an unrelated one in the new process).
I don't entirely disagree, except it's definitely an incompatible change, and so we can't just do it. We probably need to get away from file descriptors entirely for this to be anywhere near reliable, and return the HANDLE value from Let's end the discussion on this PR. Move back to the issue if needed. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
On Windows, set _O_NOINHERIT flag on file descriptors created by os.pipe() and io.WindowsConsoleIO. Add test_pipe_spawnl() to test_os. Co-authored-by: Zackery Spytz <[email protected]>
On Windows, set _O_NOINHERIT flag on file descriptors created by os.pipe() and io.WindowsConsoleIO. Add test_pipe_spawnl() to test_os. Co-authored-by: Zackery Spytz <[email protected]>
On Windows, set _O_NOINHERIT flag on file descriptors created by os.pipe() and io.WindowsConsoleIO. Add test_pipe_spawnl() to test_os. Co-authored-by: Zackery Spytz <[email protected]>
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.