Skip to content

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

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
merged 3 commits into from
Sep 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def test_return_null_without_error(self):
self.assertRegex(err.replace(b'\r', b''),
br'Fatal Python error: a function returned NULL '
br'without setting an error\n'
br'Python runtime state: initialized\n'
br'SystemError: <built-in function '
br'return_null_without_error> returned NULL '
br'without setting an error\n'
Expand Down Expand Up @@ -225,6 +226,7 @@ def test_return_result_with_error(self):
self.assertRegex(err.replace(b'\r', b''),
br'Fatal Python error: a function returned a '
br'result with an error set\n'
br'Python runtime state: initialized\n'
br'ValueError\n'
br'\n'
br'The above exception was the direct cause '
Expand Down
18 changes: 12 additions & 6 deletions Lib/test/test_faulthandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ def get_output(self, code, filename=None, fd=None):

def check_error(self, code, line_number, fatal_error, *,
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
traceback from the child process output.
Expand All @@ -110,10 +111,12 @@ def check_error(self, code, line_number, fatal_error, *,
{header} \(most recent call first\):
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,
fatal_error=fatal_error,
header=header)).strip()
header=header).strip()
if other_regex:
regex += '|' + other_regex
output, exitcode = self.get_output(code, filename=filename, fd=fd)
Expand Down Expand Up @@ -170,7 +173,8 @@ def test_fatal_error_c_thread(self):
""",
3,
'in new thread',
know_current_thread=False)
know_current_thread=False,
py_fatal_error=True)

def test_sigabrt(self):
self.check_fatal_error("""
Expand Down Expand Up @@ -226,15 +230,17 @@ def test_fatal_error(self):
faulthandler._fatal_error(b'xyz')
""",
2,
'xyz')
'xyz',
py_fatal_error=True)

def test_fatal_error_without_gil(self):
self.check_fatal_error("""
import faulthandler
faulthandler._fatal_error(b'xyz', True)
""",
2,
'xyz')
'xyz',
py_fatal_error=True)

@unittest.skipIf(sys.platform.startswith('openbsd'),
"Issue #12868: sigaltstack() doesn't work on "
Expand Down
85 changes: 59 additions & 26 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1975,13 +1975,14 @@ init_sys_streams(PyThreadState *tstate)


static void
_Py_FatalError_DumpTracebacks(int fd)
_Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp,
PyThreadState *tstate)
{
fputc('\n', stderr);
fflush(stderr);

/* 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,
Expand Down Expand Up @@ -2079,10 +2080,39 @@ fatal_output_debug(const char *msg)
}
#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
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;

if (reentrant) {
Expand All @@ -2092,45 +2122,48 @@ fatal_error(const char *prefix, const char *msg, int status)
}
reentrant = 1;

fprintf(stderr, "Fatal Python error: ");
fprintf(stream, "Fatal Python error: ");
if (prefix) {
fputs(prefix, stderr);
fputs(": ", stderr);
fputs(prefix, stream);
fputs(": ", stream);
}
if (msg) {
fputs(msg, stderr);
fputs(msg, stream);
}
else {
fprintf(stderr, "<message not set>");
fprintf(stream, "<message not set>");
}
fputs("\n", stderr);
fflush(stderr); /* it helps in Windows debug build */
fputs("\n", stream);
fflush(stream); /* it helps in Windows debug build */

/* Check if the current thread has a Python thread state
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. */
_PyRuntimeState *runtime = &_PyRuntime;
fatal_error_dump_runtime(stream, runtime);

PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
PyInterpreterState *interp = NULL;
if (tstate != NULL) {
interp = tstate->interp;
}
int has_tstate_and_gil = (tss_tstate != NULL);

/* Check if the current thread has a Python thread state
and holds the GIL.

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 an exception is set, print the exception with its traceback */
if (!_Py_FatalError_PrintExc(fd)) {
/* No exception is set, or an exception is set without traceback */
_Py_FatalError_DumpTracebacks(fd);
_Py_FatalError_DumpTracebacks(fd, interp, tss_tstate);
}
}
else {
_Py_FatalError_DumpTracebacks(fd);
_Py_FatalError_DumpTracebacks(fd, interp, tss_tstate);
}

/* The main purpose of faulthandler is to display the traceback.
Expand Down