Skip to content

Commit 1ce16fb

Browse files
authored
bpo-38070: Py_FatalError() logs runtime state (GH-16246)
1 parent d3b9041 commit 1ce16fb

File tree

3 files changed

+73
-32
lines changed

3 files changed

+73
-32
lines changed

Lib/test/test_capi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ def test_return_null_without_error(self):
198198
self.assertRegex(err.replace(b'\r', b''),
199199
br'Fatal Python error: a function returned NULL '
200200
br'without setting an error\n'
201+
br'Python runtime state: initialized\n'
201202
br'SystemError: <built-in function '
202203
br'return_null_without_error> returned NULL '
203204
br'without setting an error\n'
@@ -225,6 +226,7 @@ def test_return_result_with_error(self):
225226
self.assertRegex(err.replace(b'\r', b''),
226227
br'Fatal Python error: a function returned a '
227228
br'result with an error set\n'
229+
br'Python runtime state: initialized\n'
228230
br'ValueError\n'
229231
br'\n'
230232
br'The above exception was the direct cause '

Lib/test/test_faulthandler.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ def get_output(self, code, filename=None, fd=None):
9090

9191
def check_error(self, code, line_number, fatal_error, *,
9292
filename=None, all_threads=True, other_regex=None,
93-
fd=None, know_current_thread=True):
93+
fd=None, know_current_thread=True,
94+
py_fatal_error=False):
9495
"""
9596
Check that the fault handler for fatal errors is enabled and check the
9697
traceback from the child process output.
@@ -110,10 +111,12 @@ def check_error(self, code, line_number, fatal_error, *,
110111
{header} \(most recent call first\):
111112
File "<string>", line {lineno} in <module>
112113
"""
113-
regex = dedent(regex.format(
114+
if py_fatal_error:
115+
fatal_error += "\nPython runtime state: initialized"
116+
regex = dedent(regex).format(
114117
lineno=line_number,
115118
fatal_error=fatal_error,
116-
header=header)).strip()
119+
header=header).strip()
117120
if other_regex:
118121
regex += '|' + other_regex
119122
output, exitcode = self.get_output(code, filename=filename, fd=fd)
@@ -170,7 +173,8 @@ def test_fatal_error_c_thread(self):
170173
""",
171174
3,
172175
'in new thread',
173-
know_current_thread=False)
176+
know_current_thread=False,
177+
py_fatal_error=True)
174178

175179
def test_sigabrt(self):
176180
self.check_fatal_error("""
@@ -226,15 +230,17 @@ def test_fatal_error(self):
226230
faulthandler._fatal_error(b'xyz')
227231
""",
228232
2,
229-
'xyz')
233+
'xyz',
234+
py_fatal_error=True)
230235

231236
def test_fatal_error_without_gil(self):
232237
self.check_fatal_error("""
233238
import faulthandler
234239
faulthandler._fatal_error(b'xyz', True)
235240
""",
236241
2,
237-
'xyz')
242+
'xyz',
243+
py_fatal_error=True)
238244

239245
@unittest.skipIf(sys.platform.startswith('openbsd'),
240246
"Issue #12868: sigaltstack() doesn't work on "

Python/pylifecycle.c

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,13 +1975,14 @@ init_sys_streams(PyThreadState *tstate)
19751975

19761976

19771977
static void
1978-
_Py_FatalError_DumpTracebacks(int fd)
1978+
_Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp,
1979+
PyThreadState *tstate)
19791980
{
19801981
fputc('\n', stderr);
19811982
fflush(stderr);
19821983

19831984
/* display the current Python stack */
1984-
_Py_DumpTracebackThreads(fd, NULL, NULL);
1985+
_Py_DumpTracebackThreads(fd, interp, tstate);
19851986
}
19861987

19871988
/* Print the current exception (if an exception is set) with its traceback,
@@ -2079,10 +2080,39 @@ fatal_output_debug(const char *msg)
20792080
}
20802081
#endif
20812082

2083+
2084+
static void
2085+
fatal_error_dump_runtime(FILE *stream, _PyRuntimeState *runtime)
2086+
{
2087+
fprintf(stream, "Python runtime state: ");
2088+
if (runtime->finalizing) {
2089+
fprintf(stream, "finalizing (tstate=%p)", runtime->finalizing);
2090+
}
2091+
else if (runtime->initialized) {
2092+
fprintf(stream, "initialized");
2093+
}
2094+
else if (runtime->core_initialized) {
2095+
fprintf(stream, "core initialized");
2096+
}
2097+
else if (runtime->preinitialized) {
2098+
fprintf(stream, "preinitialized");
2099+
}
2100+
else if (runtime->preinitializing) {
2101+
fprintf(stream, "preinitializing");
2102+
}
2103+
else {
2104+
fprintf(stream, "unknown");
2105+
}
2106+
fprintf(stream, "\n");
2107+
fflush(stream);
2108+
}
2109+
2110+
20822111
static void _Py_NO_RETURN
20832112
fatal_error(const char *prefix, const char *msg, int status)
20842113
{
2085-
const int fd = fileno(stderr);
2114+
FILE *stream = stderr;
2115+
const int fd = fileno(stream);
20862116
static int reentrant = 0;
20872117

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

2095-
fprintf(stderr, "Fatal Python error: ");
2125+
fprintf(stream, "Fatal Python error: ");
20962126
if (prefix) {
2097-
fputs(prefix, stderr);
2098-
fputs(": ", stderr);
2127+
fputs(prefix, stream);
2128+
fputs(": ", stream);
20992129
}
21002130
if (msg) {
2101-
fputs(msg, stderr);
2131+
fputs(msg, stream);
21022132
}
21032133
else {
2104-
fprintf(stderr, "<message not set>");
2134+
fprintf(stream, "<message not set>");
21052135
}
2106-
fputs("\n", stderr);
2107-
fflush(stderr); /* it helps in Windows debug build */
2136+
fputs("\n", stream);
2137+
fflush(stream); /* it helps in Windows debug build */
21082138

2109-
/* Check if the current thread has a Python thread state
2110-
and holds the GIL */
2111-
PyThreadState *tss_tstate = PyGILState_GetThisThreadState();
2112-
if (tss_tstate != NULL) {
2113-
PyThreadState *tstate = _PyThreadState_GET();
2114-
if (tss_tstate != tstate) {
2115-
/* The Python thread does not hold the GIL */
2116-
tss_tstate = NULL;
2117-
}
2118-
}
2119-
else {
2120-
/* Py_FatalError() has been called from a C thread
2121-
which has no Python thread state. */
2139+
_PyRuntimeState *runtime = &_PyRuntime;
2140+
fatal_error_dump_runtime(stream, runtime);
2141+
2142+
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
2143+
PyInterpreterState *interp = NULL;
2144+
if (tstate != NULL) {
2145+
interp = tstate->interp;
21222146
}
2123-
int has_tstate_and_gil = (tss_tstate != NULL);
21242147

2148+
/* Check if the current thread has a Python thread state
2149+
and holds the GIL.
2150+
2151+
tss_tstate is NULL if Py_FatalError() is called from a C thread which
2152+
has no Python thread state.
2153+
2154+
tss_tstate != tstate if the current Python thread does not hold the GIL.
2155+
*/
2156+
PyThreadState *tss_tstate = PyGILState_GetThisThreadState();
2157+
int has_tstate_and_gil = (tss_tstate != NULL && tss_tstate == tstate);
21252158
if (has_tstate_and_gil) {
21262159
/* If an exception is set, print the exception with its traceback */
21272160
if (!_Py_FatalError_PrintExc(fd)) {
21282161
/* No exception is set, or an exception is set without traceback */
2129-
_Py_FatalError_DumpTracebacks(fd);
2162+
_Py_FatalError_DumpTracebacks(fd, interp, tss_tstate);
21302163
}
21312164
}
21322165
else {
2133-
_Py_FatalError_DumpTracebacks(fd);
2166+
_Py_FatalError_DumpTracebacks(fd, interp, tss_tstate);
21342167
}
21352168

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

0 commit comments

Comments
 (0)