bpo-38070: Py_FatalError() logs runtime state (GH-16246)

This commit is contained in:
Victor Stinner 2019-09-18 01:35:33 +02:00 committed by GitHub
parent d3b904144e
commit 1ce16fb097
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 32 deletions

View file

@ -198,6 +198,7 @@ class CAPITest(unittest.TestCase):
self.assertRegex(err.replace(b'\r', b''), self.assertRegex(err.replace(b'\r', b''),
br'Fatal Python error: a function returned NULL ' br'Fatal Python error: a function returned NULL '
br'without setting an error\n' br'without setting an error\n'
br'Python runtime state: initialized\n'
br'SystemError: <built-in function ' br'SystemError: <built-in function '
br'return_null_without_error> returned NULL ' br'return_null_without_error> returned NULL '
br'without setting an error\n' br'without setting an error\n'
@ -225,6 +226,7 @@ class CAPITest(unittest.TestCase):
self.assertRegex(err.replace(b'\r', b''), self.assertRegex(err.replace(b'\r', b''),
br'Fatal Python error: a function returned a ' br'Fatal Python error: a function returned a '
br'result with an error set\n' br'result with an error set\n'
br'Python runtime state: initialized\n'
br'ValueError\n' br'ValueError\n'
br'\n' br'\n'
br'The above exception was the direct cause ' br'The above exception was the direct cause '

View file

@ -90,7 +90,8 @@ class FaultHandlerTests(unittest.TestCase):
def check_error(self, code, line_number, fatal_error, *, def check_error(self, code, line_number, fatal_error, *,
filename=None, all_threads=True, other_regex=None, filename=None, all_threads=True, other_regex=None,
fd=None, know_current_thread=True): fd=None, know_current_thread=True,
py_fatal_error=False):
""" """
Check that the fault handler for fatal errors is enabled and check the Check that the fault handler for fatal errors is enabled and check the
traceback from the child process output. traceback from the child process output.
@ -110,10 +111,12 @@ class FaultHandlerTests(unittest.TestCase):
{header} \(most recent call first\): {header} \(most recent call first\):
File "<string>", line {lineno} in <module> File "<string>", line {lineno} in <module>
""" """
regex = dedent(regex.format( if py_fatal_error:
fatal_error += "\nPython runtime state: initialized"
regex = dedent(regex).format(
lineno=line_number, lineno=line_number,
fatal_error=fatal_error, fatal_error=fatal_error,
header=header)).strip() header=header).strip()
if other_regex: if other_regex:
regex += '|' + other_regex regex += '|' + other_regex
output, exitcode = self.get_output(code, filename=filename, fd=fd) output, exitcode = self.get_output(code, filename=filename, fd=fd)
@ -170,7 +173,8 @@ class FaultHandlerTests(unittest.TestCase):
""", """,
3, 3,
'in new thread', 'in new thread',
know_current_thread=False) know_current_thread=False,
py_fatal_error=True)
def test_sigabrt(self): def test_sigabrt(self):
self.check_fatal_error(""" self.check_fatal_error("""
@ -226,7 +230,8 @@ class FaultHandlerTests(unittest.TestCase):
faulthandler._fatal_error(b'xyz') faulthandler._fatal_error(b'xyz')
""", """,
2, 2,
'xyz') 'xyz',
py_fatal_error=True)
def test_fatal_error_without_gil(self): def test_fatal_error_without_gil(self):
self.check_fatal_error(""" self.check_fatal_error("""
@ -234,7 +239,8 @@ class FaultHandlerTests(unittest.TestCase):
faulthandler._fatal_error(b'xyz', True) faulthandler._fatal_error(b'xyz', True)
""", """,
2, 2,
'xyz') 'xyz',
py_fatal_error=True)
@unittest.skipIf(sys.platform.startswith('openbsd'), @unittest.skipIf(sys.platform.startswith('openbsd'),
"Issue #12868: sigaltstack() doesn't work on " "Issue #12868: sigaltstack() doesn't work on "

View file

@ -1975,13 +1975,14 @@ done:
static void static void
_Py_FatalError_DumpTracebacks(int fd) _Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp,
PyThreadState *tstate)
{ {
fputc('\n', stderr); fputc('\n', stderr);
fflush(stderr); fflush(stderr);
/* display the current Python stack */ /* display the current Python stack */
_Py_DumpTracebackThreads(fd, NULL, NULL); _Py_DumpTracebackThreads(fd, interp, tstate);
} }
/* Print the current exception (if an exception is set) with its traceback, /* Print the current exception (if an exception is set) with its traceback,
@ -2079,10 +2080,39 @@ fatal_output_debug(const char *msg)
} }
#endif #endif
static void
fatal_error_dump_runtime(FILE *stream, _PyRuntimeState *runtime)
{
fprintf(stream, "Python runtime state: ");
if (runtime->finalizing) {
fprintf(stream, "finalizing (tstate=%p)", runtime->finalizing);
}
else if (runtime->initialized) {
fprintf(stream, "initialized");
}
else if (runtime->core_initialized) {
fprintf(stream, "core initialized");
}
else if (runtime->preinitialized) {
fprintf(stream, "preinitialized");
}
else if (runtime->preinitializing) {
fprintf(stream, "preinitializing");
}
else {
fprintf(stream, "unknown");
}
fprintf(stream, "\n");
fflush(stream);
}
static void _Py_NO_RETURN static void _Py_NO_RETURN
fatal_error(const char *prefix, const char *msg, int status) fatal_error(const char *prefix, const char *msg, int status)
{ {
const int fd = fileno(stderr); FILE *stream = stderr;
const int fd = fileno(stream);
static int reentrant = 0; static int reentrant = 0;
if (reentrant) { if (reentrant) {
@ -2092,45 +2122,48 @@ fatal_error(const char *prefix, const char *msg, int status)
} }
reentrant = 1; reentrant = 1;
fprintf(stderr, "Fatal Python error: "); fprintf(stream, "Fatal Python error: ");
if (prefix) { if (prefix) {
fputs(prefix, stderr); fputs(prefix, stream);
fputs(": ", stderr); fputs(": ", stream);
} }
if (msg) { if (msg) {
fputs(msg, stderr); fputs(msg, stream);
} }
else { else {
fprintf(stderr, "<message not set>"); fprintf(stream, "<message not set>");
}
fputs("\n", stream);
fflush(stream); /* it helps in Windows debug build */
_PyRuntimeState *runtime = &_PyRuntime;
fatal_error_dump_runtime(stream, runtime);
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
PyInterpreterState *interp = NULL;
if (tstate != NULL) {
interp = tstate->interp;
} }
fputs("\n", stderr);
fflush(stderr); /* it helps in Windows debug build */
/* Check if the current thread has a Python thread state /* Check if the current thread has a Python thread state
and holds the GIL */ and holds the GIL.
PyThreadState *tss_tstate = PyGILState_GetThisThreadState();
if (tss_tstate != NULL) {
PyThreadState *tstate = _PyThreadState_GET();
if (tss_tstate != tstate) {
/* The Python thread does not hold the GIL */
tss_tstate = NULL;
}
}
else {
/* Py_FatalError() has been called from a C thread
which has no Python thread state. */
}
int has_tstate_and_gil = (tss_tstate != NULL);
tss_tstate is NULL if Py_FatalError() is called from a C thread which
has no Python thread state.
tss_tstate != tstate if the current Python thread does not hold the GIL.
*/
PyThreadState *tss_tstate = PyGILState_GetThisThreadState();
int has_tstate_and_gil = (tss_tstate != NULL && tss_tstate == tstate);
if (has_tstate_and_gil) { if (has_tstate_and_gil) {
/* If an exception is set, print the exception with its traceback */ /* If an exception is set, print the exception with its traceback */
if (!_Py_FatalError_PrintExc(fd)) { if (!_Py_FatalError_PrintExc(fd)) {
/* No exception is set, or an exception is set without traceback */ /* No exception is set, or an exception is set without traceback */
_Py_FatalError_DumpTracebacks(fd); _Py_FatalError_DumpTracebacks(fd, interp, tss_tstate);
} }
} }
else { else {
_Py_FatalError_DumpTracebacks(fd); _Py_FatalError_DumpTracebacks(fd, interp, tss_tstate);
} }
/* The main purpose of faulthandler is to display the traceback. /* The main purpose of faulthandler is to display the traceback.