Skip to content

Commit 47bbab9

Browse files
authored
[3.8] bpo-38070: Py_FatalError() logs runtime state (GH-16258)
* bpo-38070: _Py_DumpTraceback() writes <no Python frame> (GH-16244) When a Python thread has no frame, _Py_DumpTraceback() and _Py_DumpTracebackThreads() now write "<no Python frame>", rather than writing nothing. (cherry picked from commit 8fa3e17) * bpo-38070: Enhance _PyObject_Dump() (GH-16243) _PyObject_Dump() now dumps the object address for freed objects and objects with ob_type=NULL. (cherry picked from commit b39afb7) * bpo-38070: Add _PyRuntimeState.preinitializing (GH-16245) Add _PyRuntimeState.preinitializing field: set to 1 while Py_PreInitialize() is running. _PyRuntimeState: rename also pre_initialized field to preinitialized. (cherry picked from commit d3b9041) * bpo-38070: Py_FatalError() logs runtime state (GH-16246) (cherry picked from commit 1ce16fb)
1 parent 7a2f687 commit 47bbab9

File tree

6 files changed

+104
-49
lines changed

6 files changed

+104
-49
lines changed

Include/internal/pycore_pystate.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,15 +193,20 @@ struct _gilstate_runtime_state {
193193
/* Full Python runtime state */
194194

195195
typedef struct pyruntimestate {
196-
/* Is Python pre-initialized? Set to 1 by Py_PreInitialize() */
197-
int pre_initialized;
196+
/* Is running Py_PreInitialize()? */
197+
int preinitializing;
198+
199+
/* Is Python preinitialized? Set to 1 by Py_PreInitialize() */
200+
int preinitialized;
198201

199202
/* Is Python core initialized? Set to 1 by _Py_InitializeCore() */
200203
int core_initialized;
201204

202205
/* Is Python fully initialized? Set to 1 by Py_Initialize() */
203206
int initialized;
204207

208+
/* Set by Py_FinalizeEx(). Only reset to NULL if Py_Initialize()
209+
is called again. */
205210
PyThreadState *finalizing;
206211

207212
struct pyinterpreters {
@@ -244,7 +249,7 @@ typedef struct pyruntimestate {
244249
} _PyRuntimeState;
245250

246251
#define _PyRuntimeState_INIT \
247-
{.pre_initialized = 0, .core_initialized = 0, .initialized = 0}
252+
{.preinitialized = 0, .core_initialized = 0, .initialized = 0}
248253
/* Note: _PyRuntimeState_INIT sets other fields to 0/NULL */
249254

250255
PyAPI_DATA(_PyRuntimeState) _PyRuntime;

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 "

Objects/object.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -464,15 +464,15 @@ void
464464
_PyObject_Dump(PyObject* op)
465465
{
466466
if (op == NULL) {
467-
fprintf(stderr, "<NULL object>\n");
467+
fprintf(stderr, "<object at NULL>\n");
468468
fflush(stderr);
469469
return;
470470
}
471471

472472
if (_PyObject_IsFreed(op)) {
473473
/* It seems like the object memory has been freed:
474474
don't access it to prevent a segmentation fault. */
475-
fprintf(stderr, "<Freed object>\n");
475+
fprintf(stderr, "<object at %p is freed>\n", op);
476476
return;
477477
}
478478

@@ -2162,18 +2162,19 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
21622162
fflush(stderr);
21632163

21642164
if (obj == NULL) {
2165-
fprintf(stderr, "<NULL object>\n");
2165+
fprintf(stderr, "<object at NULL>\n");
21662166
}
21672167
else if (_PyObject_IsFreed(obj)) {
21682168
/* It seems like the object memory has been freed:
21692169
don't access it to prevent a segmentation fault. */
2170-
fprintf(stderr, "<object: freed>\n");
2170+
fprintf(stderr, "<object at %p is freed>\n", obj);
21712171
}
21722172
else if (Py_TYPE(obj) == NULL) {
2173-
fprintf(stderr, "<object: ob_type=NULL>\n");
2173+
fprintf(stderr, "<object at %p: ob_type=NULL>\n", obj);
21742174
}
21752175
else if (_PyObject_IsFreed((PyObject *)Py_TYPE(obj))) {
2176-
fprintf(stderr, "<object: freed type %p>\n", (void *)Py_TYPE(obj));
2176+
fprintf(stderr, "<object at %p: type at %p is freed>\n",
2177+
obj, (void *)Py_TYPE(obj));
21772178
}
21782179
else {
21792180
/* Display the traceback where the object has been allocated.

Python/pylifecycle.c

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -719,11 +719,15 @@ _Py_PreInitializeFromPyArgv(const PyPreConfig *src_config, const _PyArgv *args)
719719
}
720720
_PyRuntimeState *runtime = &_PyRuntime;
721721

722-
if (runtime->pre_initialized) {
722+
if (runtime->preinitialized) {
723723
/* If it's already configured: ignored the new configuration */
724724
return _PyStatus_OK();
725725
}
726726

727+
/* Note: preinitialized remains 1 on error, it is only set to 0
728+
at exit on success. */
729+
runtime->preinitializing = 1;
730+
727731
PyPreConfig config;
728732
_PyPreConfig_InitFromPreConfig(&config, src_config);
729733

@@ -737,7 +741,8 @@ _Py_PreInitializeFromPyArgv(const PyPreConfig *src_config, const _PyArgv *args)
737741
return status;
738742
}
739743

740-
runtime->pre_initialized = 1;
744+
runtime->preinitializing = 0;
745+
runtime->preinitialized = 1;
741746
return _PyStatus_OK();
742747
}
743748

@@ -777,7 +782,7 @@ _Py_PreInitializeFromConfig(const PyConfig *config,
777782
}
778783
_PyRuntimeState *runtime = &_PyRuntime;
779784

780-
if (runtime->pre_initialized) {
785+
if (runtime->preinitialized) {
781786
/* Already initialized: do nothing */
782787
return _PyStatus_OK();
783788
}
@@ -1961,13 +1966,14 @@ init_sys_streams(PyInterpreterState *interp)
19611966

19621967

19631968
static void
1964-
_Py_FatalError_DumpTracebacks(int fd)
1969+
_Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp,
1970+
PyThreadState *tstate)
19651971
{
19661972
fputc('\n', stderr);
19671973
fflush(stderr);
19681974

19691975
/* display the current Python stack */
1970-
_Py_DumpTracebackThreads(fd, NULL, NULL);
1976+
_Py_DumpTracebackThreads(fd, interp, tstate);
19711977
}
19721978

19731979
/* Print the current exception (if an exception is set) with its traceback,
@@ -2062,10 +2068,39 @@ fatal_output_debug(const char *msg)
20622068
}
20632069
#endif
20642070

2071+
2072+
static void
2073+
fatal_error_dump_runtime(FILE *stream, _PyRuntimeState *runtime)
2074+
{
2075+
fprintf(stream, "Python runtime state: ");
2076+
if (runtime->finalizing) {
2077+
fprintf(stream, "finalizing (tstate=%p)", runtime->finalizing);
2078+
}
2079+
else if (runtime->initialized) {
2080+
fprintf(stream, "initialized");
2081+
}
2082+
else if (runtime->core_initialized) {
2083+
fprintf(stream, "core initialized");
2084+
}
2085+
else if (runtime->preinitialized) {
2086+
fprintf(stream, "preinitialized");
2087+
}
2088+
else if (runtime->preinitializing) {
2089+
fprintf(stream, "preinitializing");
2090+
}
2091+
else {
2092+
fprintf(stream, "unknown");
2093+
}
2094+
fprintf(stream, "\n");
2095+
fflush(stream);
2096+
}
2097+
2098+
20652099
static void _Py_NO_RETURN
20662100
fatal_error(const char *prefix, const char *msg, int status)
20672101
{
2068-
const int fd = fileno(stderr);
2102+
FILE *stream = stderr;
2103+
const int fd = fileno(stream);
20692104
static int reentrant = 0;
20702105

20712106
if (reentrant) {
@@ -2075,45 +2110,48 @@ fatal_error(const char *prefix, const char *msg, int status)
20752110
}
20762111
reentrant = 1;
20772112

2078-
fprintf(stderr, "Fatal Python error: ");
2113+
fprintf(stream, "Fatal Python error: ");
20792114
if (prefix) {
2080-
fputs(prefix, stderr);
2081-
fputs(": ", stderr);
2115+
fputs(prefix, stream);
2116+
fputs(": ", stream);
20822117
}
20832118
if (msg) {
2084-
fputs(msg, stderr);
2119+
fputs(msg, stream);
20852120
}
20862121
else {
2087-
fprintf(stderr, "<message not set>");
2122+
fprintf(stream, "<message not set>");
20882123
}
2089-
fputs("\n", stderr);
2090-
fflush(stderr); /* it helps in Windows debug build */
2124+
fputs("\n", stream);
2125+
fflush(stream); /* it helps in Windows debug build */
20912126

2092-
/* Check if the current thread has a Python thread state
2093-
and holds the GIL */
2094-
PyThreadState *tss_tstate = PyGILState_GetThisThreadState();
2095-
if (tss_tstate != NULL) {
2096-
PyThreadState *tstate = _PyThreadState_GET();
2097-
if (tss_tstate != tstate) {
2098-
/* The Python thread does not hold the GIL */
2099-
tss_tstate = NULL;
2100-
}
2101-
}
2102-
else {
2103-
/* Py_FatalError() has been called from a C thread
2104-
which has no Python thread state. */
2127+
_PyRuntimeState *runtime = &_PyRuntime;
2128+
fatal_error_dump_runtime(stream, runtime);
2129+
2130+
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
2131+
PyInterpreterState *interp = NULL;
2132+
if (tstate != NULL) {
2133+
interp = tstate->interp;
21052134
}
2106-
int has_tstate_and_gil = (tss_tstate != NULL);
21072135

2136+
/* Check if the current thread has a Python thread state
2137+
and holds the GIL.
2138+
2139+
tss_tstate is NULL if Py_FatalError() is called from a C thread which
2140+
has no Python thread state.
2141+
2142+
tss_tstate != tstate if the current Python thread does not hold the GIL.
2143+
*/
2144+
PyThreadState *tss_tstate = PyGILState_GetThisThreadState();
2145+
int has_tstate_and_gil = (tss_tstate != NULL && tss_tstate == tstate);
21082146
if (has_tstate_and_gil) {
21092147
/* If an exception is set, print the exception with its traceback */
21102148
if (!_Py_FatalError_PrintExc(fd)) {
21112149
/* No exception is set, or an exception is set without traceback */
2112-
_Py_FatalError_DumpTracebacks(fd);
2150+
_Py_FatalError_DumpTracebacks(fd, interp, tss_tstate);
21132151
}
21142152
}
21152153
else {
2116-
_Py_FatalError_DumpTracebacks(fd);
2154+
_Py_FatalError_DumpTracebacks(fd, interp, tss_tstate);
21172155
}
21182156

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

Python/traceback.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -797,12 +797,15 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
797797
PyFrameObject *frame;
798798
unsigned int depth;
799799

800-
if (write_header)
800+
if (write_header) {
801801
PUTS(fd, "Stack (most recent call first):\n");
802+
}
802803

803804
frame = _PyThreadState_GetFrame(tstate);
804-
if (frame == NULL)
805+
if (frame == NULL) {
806+
PUTS(fd, "<no Python frame>\n");
805807
return;
808+
}
806809

807810
depth = 0;
808811
while (frame != NULL) {
@@ -870,9 +873,9 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
870873
Python thread state of the current thread.
871874
872875
PyThreadState_Get() doesn't give the state of the thread that caused
873-
the fault if the thread released the GIL, and so this function
874-
cannot be used. Read the thread specific storage (TSS) instead: call
875-
PyGILState_GetThisThreadState(). */
876+
the fault if the thread released the GIL, and so
877+
_PyThreadState_GET() cannot be used. Read the thread specific
878+
storage (TSS) instead: call PyGILState_GetThisThreadState(). */
876879
current_tstate = PyGILState_GetThisThreadState();
877880
}
878881

0 commit comments

Comments
 (0)