classification
Title: _io._WindowsConsoleIO breaks in the face of fd redirection
Type: behavior Stage: backport needed
Components: IO, Windows Versions: Python 3.10, Python 3.9, Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Segev Finer, eryksun, paul.moore, steve.dower, tim.golden, zach.ware
Priority: normal Keywords:

Created on 2017-06-02 16:56 by Segev Finer, last changed 2021-04-23 22:04 by steve.dower.

Pull Requests
URL Status Linked Edit
PR 1927 merged Segev Finer, 2017-06-03 14:01
Messages (5)
msg295041 - (view) Author: Segev Finer (Segev Finer) * Date: 2017-06-02 16:56
_WindowsConsoleIO works by taking the handle out of the file descriptors of the stdio handles (get_osfhandle) and reading/writing to it directly using ReadConsoleW/WriteConsoleW.

The problem is that some Python code wants to do file descriptor level redirection by overwriting the standard file descriptors using dup2. The problem is that this is also going to close the handle that Python itself is going to use to read/write to the console. Leading to the following:

    >>> fd = os.open('stdout.txt', os.O_CREAT | os.O_WRONLY)
    >>> os.dup2(fd, 1)
    >>> print('spam')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    OSError: [WinError 87] The parameter is incorrect  # "OSError: [WinError 6] The handle is invalid" after https://bugs.python.org/issue30544

This manifests itself for example in Pytest which does fd redirection to capture test output. See https://github.com/pytest-dev/py/issues/103 for the issue. And see https://github.com/pytest-dev/pytest/pull/2462 for an WIP attempt to workaround this.

This issue also impacts other code that uses console handles itself like colorama: https://github.com/pytest-dev/pytest/issues/2465. Though that code is likely going to have to deal with this by itself.

Those file descriptors are an emulation implemented by the Universal CRT and you can see their implementation in your copy of the Windows SDK. It's quite possible that this doesn't happen in older CRT versions either since colorama doesn't seem to break on Python 2.7 for example.

One way I can think working around this is that Python will DuplicateHandle the console handles so that even if dup2 closes the original ones it will keep on working. We can also switch to calling _get_osfhandle always instead of caching the handle, it will break when the fd is redirected to a non-console. But so does _WindowsConsoleIO in general since it will try to continue writing to the console despite the redirection, meaning that Python code doing redirection has to handle sys.std* anyhow. Though I'm not sure about the performance of constantly calling _get_osfhandle. And their yet might be other ways to solve this.

Also see comment by eryksun https://bugs.python.org/msg294988

Solving this in CPython will remove the need for hacks like the PR I referenced.
msg295047 - (view) Author: Steve Dower (steve.dower) * (Python committer) Date: 2017-06-02 17:59
> We can also switch to calling _get_osfhandle always instead of caching the handle, it will break when the fd is redirected to a non-console. But so does _WindowsConsoleIO in general since it will try to continue writing to the console despite the redirection, meaning that Python code doing redirection has to handle sys.std* anyhow.

This might be the best approach, ultimately. I bet there's an optimization here, though it's not obvious.

Probably the main issue here is readline not properly handling sys.std*. If that was fixed, I think many of these problems (at least at the interactive prompt) would simply go away.
msg388796 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2021-03-16 01:50
PR 1927 is a definite improvement. By using _get_osfhandle() instead of caching the handle value, it guarantees that access to the original console file can be saved and restored via `fd_save = dup(fd)` and `dup2(fd_save, fd)`. It also allows duping a new open of CON, CONIN$, or CONOUT$, or even a new screen buffer from CreateConsoleScreenBuffer(), to the existing fileno(), and the _WindowsConsoleIO() instance will march along none the wiser.
msg391745 - (view) Author: Steve Dower (steve.dower) * (Python committer) Date: 2021-04-23 22:00
New changeset 5e437fb872279960992c9a07f1a4c051b4948c53 by Segev Finer in branch 'master':
bpo-30555: Fix WindowsConsoleIO fails in the presence of fd redirection (GH-1927)
https://github.com/python/cpython/commit/5e437fb872279960992c9a07f1a4c051b4948c53
msg391746 - (view) Author: Steve Dower (steve.dower) * (Python committer) Date: 2021-04-23 22:04
Thanks for persisting with this change.

Looks like backports will have to be manual, and I'm not going to get to them today myself, but if someone else wants to do it then feel free.

I'm pretty sure this won't have any (negative) visible impact, but just in case, let's let a 3.10 release go out with it before merging the backports. I'd rather have no change in earlier versions than a regression.
History
Date User Action Args
2021-04-23 22:04:03steve.dowersetmessages: + msg391746
stage: patch review -> backport needed
2021-04-23 22:00:37steve.dowersetmessages: + msg391745
2021-03-16 05:04:10eryksunlinkissue34187 superseder
2021-03-16 01:50:21eryksunsetstage: patch review
messages: + msg388796
versions: + Python 3.8, Python 3.9, Python 3.10, - Python 3.6, Python 3.7
2017-06-03 14:01:11Segev Finersetpull_requests: + pull_request2007
2017-06-02 17:59:01steve.dowersetmessages: + msg295047
2017-06-02 16:59:57Segev Finersetnosy: + eryksun
2017-06-02 16:56:53Segev Finercreate