Repository navigation
PyOS_InterruptOccurred() now requires to hold the GIL: PyOS_Readline() crash #85003
Copy link
Copy link
Closed
Labels
3.10 (EOL)end of lifeend of life3.8 (EOL)end of lifeend of life3.9 (EOL)end of lifeend of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee = None
closed_at = <Date 2020-06-10.17:46:11.537>
created_at = <Date 2020-05-30.14:19:46.754>
labels = ['interpreter-core', '3.10', '3.8', '3.9', 'type-crash']
title = 'PyOS_InterruptOccurred() now requires to hold the GIL: PyOS_Readline() crash'
updated_at = <Date 2020-06-10.20:13:01.555>
user = 'https://github.com/JelleZijlstra'bugs.python.org fields:
activity = <Date 2020-06-10.20:13:01.555>
actor = 'vstinner'
assignee = 'none'
closed = True
closed_date = <Date 2020-06-10.17:46:11.537>
closer = 'vstinner'
components = ['Interpreter Core']
creation = <Date 2020-05-30.14:19:46.754>
creator = 'JelleZijlstra'
dependencies = []
files = []
hgrepos = []
issue_num = 40826
keywords = ['patch', '3.9regression']
message_count = 31.0
messages = ['370386', '370387', '370388', '370392', '370549', '370550', '370552', '370553', '370554', '370555', '370556', '370569', '370575', '370576', '370661', '370676', '370679', '370682', '370683', '370692', '370694', '370696', '370716', '370718', '371169', '371223', '371224', '371226', '371227', '371233', '371239']
nosy_count = 9.0
nosy_names = ['db3l', 'vstinner', 'benjamin.peterson', 'stutzbach', 'serhiy.storchaka', 'eryksun', 'JelleZijlstra', 'corona10', 'remi.lapeyre']
pr_nums = ['20549', '20571', '20578', '20579', '20599', '20613', '20616', '20618', '20779', '20785', '20787']
priority = 'normal'
resolution = 'fixed'
stage = 'resolved'
status = 'closed'
superseder = None
type = 'crash'
url = 'https://bugs.python.org/issue40826'
versions = ['Python 3.8', 'Python 3.9', 'Python 3.10']Metadata
Metadata
Assignees
Labels
3.10 (EOL)end of lifeend of life3.8 (EOL)end of lifeend of life3.9 (EOL)end of lifeend of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Activity
JelleZijlstra commentedon May 30, 2020
Same happens with Python 3.9.0b1+ (heads/3.9:588efc29c5d, May 30 2020, 14:16:10) (current HEAD of the 3.9 branch).
In previous versions of Python this would exit the interpreter but not segfault.
corona10 commentedon May 30, 2020
Thanks for report, I can reproduce it
serhiy-storchaka commentedon May 30, 2020
Simpler reproducer:
[-]Segfaults on io.FileIO(0).name in 3.9[/-][+]Segfaults when close file descriptor 0[/+][-]Segfaults on io.FileIO(0).name in 3.9[/-][+]Segfaults when close file descriptor 0[/+]corona10 commentedon May 30, 2020
FYI this change fix this issue.
vstinner commentedon Jun 1, 2020
The following change modified PyOS_InterruptOccurred() to require the hold the GIL:
commit d831688
Author: Victor Stinner <vstinner@python.org>
Date: Fri Mar 20 14:50:35 2020 +0100
--
PyOS_InterruptOccurred() is part of the limited C API, but it's not even documented :-( So it's not easy to say if it's a backward incompatible change. At least, as the author of the change, I can say that the change was deliberate.
bpo-40010 rationale is that only the main thread of the main interpreter must call Python signal handlers. So when another thread or another interpreter (running the main thread) asks "should the Python signal handlers be executed", the answer is always "no", even if yes, Python got a signal (which requires to execute at least one Python signal handler).
The change is that PyOS_InterruptOccurred() now requires to hold the GIL to get current Python thread state: _PyThreadState_GET() returns NULL when the GIL is released.
Modifying PyOS_InterruptOccurred() to always return 0 if the GIL is released (if _PyThreadState_GET() returns NULL) is wrong: if the function is called by the main thread of the main interpreter, it must return 1.
--
By the way, I rewrote the C signal handler in Python 3.9 to not require to get the current Python thread state. In Python 3.8, it was modified and started to require the current Python thread if writing into the wakeup file descriptor failed to schedule a "pending call".
commit b54a99d
Author: Victor Stinner <vstinner@python.org>
Date: Wed Apr 8 23:35:05 2020 +0200
22 remaining items
eryksun commentedon Jun 4, 2020
The OS error mode is inherited, unless the child is created with CREATE_DEFAULT_ERROR_MODE flag. But the CRT error mode isn't inherited. (For the spawn* family, in principle the CRT could pass its error modes in reserved STARTUPINFO fields, like it does for file descriptors, but it doesn't do that, and subprocess wouldn't be privy to that protocol anyway.)
For example:
>>> import sys, subprocess >>> from msvcrt import * >>> SEM_FAILCRITICALERRORS, CRTDBG_MODE_FILE, CRTDBG_MODE_WNDW (1, 1, 4) >>> SetErrorMode(SEM_FAILCRITICALERRORS) 0 >>> CrtSetReportMode(CRT_ERROR, CRTDBG_MODE_FILE) 4The return values are the previous values, which are respectively 0 (default) for the OS and CRTDBG_MODE_WNDW (message box) for the CRT. Now check the initial values in a child process:
>>> from msvcrt import * >>> SetErrorMode(SEM_FAILCRITICALERRORS) 1 >>> CrtSetReportMode(CRT_ERROR, CRTDBG_MODE_FILE) 4The OS error mode of the parent was inherited, but the parent's CRT error mode was not.
libregrtest has a convenient function to suppress error reporting in the child. For example:
>>> import os >>> from test.libregrtest import setup >>> setup.suppress_msvcrt_asserts(0) >>> os.close(0) >>> 0vstinner commentedon Jun 4, 2020
I am confused. Code which uses the closed file descriptor 0 uses "_Py_BEGIN_SUPPRESS_IPH":
and
Macros defined as:
static void __cdecl _silent_invalid_parameter_handler(
wchar_t const* expression,
wchar_t const* function,
wchar_t const* file,
unsigned int line,
uintptr_t pReserved) { }
_invalid_parameter_handler _Py_silent_invalid_parameter_handler = _silent_invalid_parameter_handler;
The purpose of _Py_BEGIN_SUPPRESS_IPH is to suppress such popup, no?
eryksun commentedon Jun 4, 2020
That macro suppresses the CRT's invalid parameter handler, which by default terminates abnormally with a fastfail (status code 0xC0000409), even in a release build.
In a debug build, we still have message boxes from failed asserts that call _CrtDbgReportW. For example, the following stack trace shows _CrtDbgReportW called from _get_osfhandle:
_get_osfhandle validates that the fd is open via
which uses the following macro:
In a debug build, _ASSERT_EXPR expands as:
which is where _CrtDbgReportW gets called. By default it reports the assertion failure with a message box. The suppression of this in libregrtest either ignores this report or sends it to stderr:
msvcrt.CrtSetReportMode and msvcrt.CrtSetReportFile only exist in a debug build.
db3l commentedon Jun 10, 2020
So a script I use on the buildbot intended to prevent hanging on assertions was failing to work in this particular case, which I've corrected. That changes the failure mode for new Win10 buildbot runs.
The test in question (test_close_stdin) still fails, but does so immediately (return code not 0, as the child process aborts) rather than timing out.
If the assertion is expected, and something to be ignored, the test itself still needs to handle that.
vstinner commentedon Jun 10, 2020
New changeset f6e58ae by Victor Stinner in branch 'master':
bpo-40826: Fix test_repl.test_close_stdin() on Windows (GH-20779)
f6e58ae
vstinner commentedon Jun 10, 2020
Thanks for the bug report David Bolen and thanks for the analysis Eryk Sun. The bug should be be fixed in the master branch, and backports to 3.8 and 3.9 are coming.
vstinner commentedon Jun 10, 2020
New changeset 4a4f660 by Victor Stinner in branch '3.9':
bpo-40826: Fix test_repl.test_close_stdin() on Windows (GH-20779) (GH-20785)
4a4f660
vstinner commentedon Jun 10, 2020
New changeset c7a6c7b by Victor Stinner in branch '3.8':
bpo-40826: Fix test_repl.test_close_stdin() on Windows (GH-20779) (GH-20785) (GH-20787)
c7a6c7b
eryksun commentedon Jun 10, 2020
Why doesn't SuppressCrashReport suppress the hard error dialog (i.e. SEM_FAILCRITICALERRORS)? This dialog gets created by the NtRaiseHardError system call. For example:
With the default OS error mode, the following raises a hard error dialog for STATUS_UNSUCCESSFUL (0xC0000001), with abort/retry/ignore options:
>>> NtRaiseHardError(0xC000_0001, 0, 0, None, 0, response) 0 >>> response[0] 2The response value is limited to abort (2), retry (7), ignore (4) -- or simply returned (0) when the process hard error mode is disabled:
>>> msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS) 0 >>> NtRaiseHardError(0xC000_0001, 0, 0, None, 0, response) 0 >>> response[0] 0NtRaiseHardError also checks for an error-mode override flag (0x1000_0000) in the status code, which is used in cases such as WinAPI FatalAppExitW. For example, the following will raise the dialog regardless of the hard error mode:
>>> NtRaiseHardError(0xC000_0001 | 0x1000_0000, 0, 0, None, 0, response) 0 >>> response[0] 2vstinner commentedon Jun 10, 2020
Would you mind to open a separated issue?