Skip to content

bpo-30555: Fix WindowsConsoleIO errors in the presence of fd redirection #1927

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

Merged

Conversation

segevfiner
Copy link
Contributor

@segevfiner segevfiner commented Jun 3, 2017

This works by not caching the handle and instead getting the handle from the file descriptor each time, so that if the actual handle changes by fd redirection closing/opening the console handle beneath our feet, we will keep working correctly.

I think I also fixed some resource handling issues along the way since ucrt/msvcrt fds own the HANDLEs they use and this module tried to close them itself.

See pytest-dev/py#103 for the issue it causes in Pytest. Also see: pytest-dev/pytest#2462. I was able to run the Pytest test suite, and my own little test described in the PR, to completion after this change.

Having your console handle closed by fd redirection is likely a change in the ucrt/msvcrt or a change in newer Windows versions (Console handles were rewritten in Windows 8).

This likely also affects any other module caching GetStdHandle or _get_osfhandle. For example colorama (pytest-dev/pytest#2465 - tartley/colorama#131), and I can think of "pyreadline", "win-unicode-console" and "click" probably suffering from the same issue but I haven't tested them.

I'm also not sure of the performance implications of this change.

This affects Python 3.6-3.7

P.S. python -m test -R 3:2 test.test_winconsoleio reports ref leaks on master for me 😛

CC @zooba @eryksun

@mention-bot
Copy link

@segevfiner, thanks for your PR! By analyzing the history of the files in this pull request, we identified @zooba, @benjaminp and @serhiy-storchaka to be potential reviewers.

@eryksun
Copy link
Contributor

eryksun commented Jun 4, 2017

Most of the reference leaks are due to initializing decodedname = Py_None followed by Py_INCREF(decodedname). It should be initialized as NULL.

Some of the reported ref leaks are from calling getwindowsversion in test_conout_path. This call can be moved out of the test by using a global variable, or by splitting the test up like this:

def conout_path_test(self, dos_device):
    temp_path = tempfile.mkdtemp()
    self.addCleanup(support.rmtree, temp_path)
    conout_path = os.path.join(temp_path, 'CONOUT$')

    with open(conout_path, 'wb', buffering=0) as f:
        if dos_device:
            self.assertIsInstance(f, ConIO)
        else:
            self.assertNotIsInstance(f, ConIO)

@unittest.skipIf(sys.getwindowsversion()[:2] <= (6, 1),
    "Windows 7 and earlier resolve CONOUT$ in a directory as a disk file")
def test_conout_path_device(self):
    self.conout_path_test(dos_device=True)

@unittest.skipIf(sys.getwindowsversion()[:2] > (6, 1),
    "Windows 8 and later resolve CONOUT$ in a directory as a DOS device")
def test_conout_path_file(self):
    self.conout_path_test(dos_device=False)

@@ -133,6 +133,21 @@ char _PyIO_get_console_type(PyObject *path_or_fd) {
return m;
}

static HANDLE py_get_osfhandle(int fd)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_Py_get_osfhandle, _Py_get_osfhandle_noraise, _Py_open_osfhandle, and _Py_open_osfhandle_noraise could be added in Python/fileutils.c. These CRT functions are called in fileutils.c, msvcrtmodule.c, posixmodule.c, mmapmodule.c, myreadline.c, _testconsole.c, and here.

Copy link
Contributor Author

@segevfiner segevfiner Jun 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

Do you wish me to:

  1. Do this entire refactor right here in this PR.
  2. Add the functions and only use them in winconsoleio.c and _testconsole.c which I touched in this PR and leave the rest of the modules to a separate PR.
  3. Leave this to a separate PR entirely.

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented this, with the full replacement in a separate commit just in case it's frowned upon by a core developer during review.

@segevfiner
Copy link
Contributor Author

segevfiner commented Jun 4, 2017

@eryksun So the leaks are real than, nice find! 😄 I can submit a PR/issue with your suggested fix if it helps you or just leave it to you. Why would calling sys.getwindowsversion in a test function leak though? Does that function leak? Or is there something else at play?

EDIT: I can also apply your fix right here if you wish me to.

@eryksun
Copy link
Contributor

eryksun commented Jun 4, 2017

It should be ok to fix the _WindowsConsoleIO leak and rewrite the test in this issue. The ref leak in getwindowsversion needs a new issue. If you want to work on it, the problem is that it calls PyTuple_Pack directly on the int objects created from realMajor, etc. PyTuple_Pack increments the ref count of each object instead of stealing it.

…tion

This works by not caching the handle and instead getting the handle from
the file descriptor each time, so that if the actual handle changes by
fd redirection closing/opening the console handle beneath our feet, we
will keep working correctly.

I think I also fixed some resource handling issues along the way since
ucrt/msvcrt fds *own* the HANDLEs they use and this module tried to
close them itself.
The change in test_winconsoleio.py is only to avoid the ref leak in
`sys.getwindowsversion`.
@segevfiner segevfiner force-pushed the bpo-30555-windowsconsoleio-fd-redirection branch from ac3af9e to aaf6020 Compare June 4, 2017 18:35
@@ -117,7 +117,15 @@ PyAPI_FUNC(int) _Py_dup(int fd);
PyAPI_FUNC(int) _Py_get_blocking(int fd);

PyAPI_FUNC(int) _Py_set_blocking(int fd, int blocking);
#endif /* !MS_WINDOWS */
#else /* MS_WINDOWS */
PyAPI_FUNC(intptr_t) _Py_get_osfhandle_noraise(int fd);
Copy link
Contributor

@eryksun eryksun Jun 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought one of benefits would be using HANDLE in the signature instead of intptr_t, to avoid all of the casts. I guess we can't. How about void * like _PyOS_SigintEvent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think CPython explicity doesn't include Windows.h anywhere in it's public headers. fileutils.h is included by Python.h so I don't think I can really include Windows.h there. Ae such I don't have HANDLE defined and assuming that someone included Windows.h by himself before this header breaks horribly of course.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HANDLE is a typedef for void *, so we can use void * instead, like _PyOS_SigintEvent does.

@segevfiner segevfiner force-pushed the bpo-30555-windowsconsoleio-fd-redirection branch from 50ff023 to 7727bc5 Compare June 5, 2017 17:04
@@ -167,15 +167,7 @@ static long
msvcrt_open_osfhandle_impl(PyObject *module, intptr_t handle, int flags)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clinic definition could be updated here, e.g. to use a HANDLE type based on void *:

class HANDLE_converter(CConverter):
    type = 'void *'
    format_unit = '"_Py_PARSE_INTPTR"'

class HANDLE_return_converter(CReturnConverter):
    type = 'void *'

    def render(self, function, data):
        self.declare(data)
        self.err_occurred_if("_return_value == INVALID_HANDLE_VALUE", data)
        data.return_conversion.append(
            'return_value = PyLong_FromVoidPtr(_return_value);\n')

Then change the open_osfhandle parameter to handle: HANDLE and the get_osfhandle return type to -> HANDLE.

@segevfiner
Copy link
Contributor Author

Merged from master since @Haypo fixed the ref leak separately in #2003.

@segevfiner
Copy link
Contributor Author

Merged from master due to argument clinic changes.

@segevfiner segevfiner requested a review from a team as a code owner December 16, 2017 10:17
@segevfiner segevfiner force-pushed the bpo-30555-windowsconsoleio-fd-redirection branch from c3953d5 to 2d252ab Compare December 16, 2017 10:26
@zooba zooba merged commit 5e437fb into python:master Apr 23, 2021
@zooba
Copy link
Member

zooba commented Apr 23, 2021

Thanks for persisting with this, and sorry for taking so long! Let's put it in and deal with any fallout in 3.10 before backporting.

@miss-islington
Copy link
Contributor

Thanks @segevfiner for the PR, and @zooba for merging it 🌮🎉.. I'm working now to backport this PR to: 3.9.
🐍🍒⛏🤖

@miss-islington
Copy link
Contributor

Thanks @segevfiner for the PR, and @zooba for merging it 🌮🎉.. I'm working now to backport this PR to: 3.8.
🐍🍒⛏🤖

@miss-islington
Copy link
Contributor

Sorry, @segevfiner and @zooba, I could not cleanly backport this to 3.9 due to a conflict.
Please backport using cherry_picker on command line.
cherry_picker 5e437fb872279960992c9a07f1a4c051b4948c53 3.9

@miss-islington
Copy link
Contributor

Sorry @segevfiner and @zooba, I had trouble checking out the 3.8 backport branch.
Please backport using cherry_picker on command line.
cherry_picker 5e437fb872279960992c9a07f1a4c051b4948c53 3.8

@miss-islington
Copy link
Contributor

Thanks @segevfiner for the PR, and @zooba for merging it 🌮🎉.. I'm working now to backport this PR to: 3.8.
🐍🍒⛏🤖

@miss-islington
Copy link
Contributor

Thanks @segevfiner for the PR, and @zooba for merging it 🌮🎉.. I'm working now to backport this PR to: 3.9.
🐍🍒⛏🤖 I'm not a witch! I'm not a witch!

@miss-islington
Copy link
Contributor

Sorry, @segevfiner and @zooba, I could not cleanly backport this to 3.9 due to a conflict.
Please backport using cherry_picker on command line.
cherry_picker 5e437fb872279960992c9a07f1a4c051b4948c53 3.9

@miss-islington
Copy link
Contributor

Sorry @segevfiner and @zooba, I had trouble checking out the 3.8 backport branch.
Please backport using cherry_picker on command line.
cherry_picker 5e437fb872279960992c9a07f1a4c051b4948c53 3.8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants