Skip to content

Commit d0c95ed

Browse files
committed
Export _Py_DumpTraceback and _Py_DumpTracebackThreads as PyUnstable_ functions
These functions stopped being exported in #107215. However, they are the only way to print a Python stacktrace safely from a signal handler, making them very useful for extensions. Re-export them as PyUnstable functions.
1 parent 6acaf65 commit d0c95ed

File tree

9 files changed

+63
-67
lines changed

9 files changed

+63
-67
lines changed

Include/internal/pycore_traceback.h

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,55 +14,6 @@ PyAPI_FUNC(int) _Py_DisplaySourceLine(PyObject *, PyObject *, int, int, int *, P
1414
// Export for 'pyexact' shared extension
1515
PyAPI_FUNC(void) _PyTraceback_Add(const char *, const char *, int);
1616

17-
/* Write the Python traceback into the file 'fd'. For example:
18-
19-
Traceback (most recent call first):
20-
File "xxx", line xxx in <xxx>
21-
File "xxx", line xxx in <xxx>
22-
...
23-
File "xxx", line xxx in <xxx>
24-
25-
This function is written for debug purpose only, to dump the traceback in
26-
the worst case: after a segmentation fault, at fatal error, etc. That's why,
27-
it is very limited. Strings are truncated to 100 characters and encoded to
28-
ASCII with backslashreplace. It doesn't write the source code, only the
29-
function name, filename and line number of each frame. Write only the first
30-
100 frames: if the traceback is truncated, write the line " ...".
31-
32-
This function is signal safe. */
33-
34-
extern void _Py_DumpTraceback(
35-
int fd,
36-
PyThreadState *tstate);
37-
38-
/* Write the traceback of all threads into the file 'fd'. current_thread can be
39-
NULL.
40-
41-
Return NULL on success, or an error message on error.
42-
43-
This function is written for debug purpose only. It calls
44-
_Py_DumpTraceback() for each thread, and so has the same limitations. It
45-
only write the traceback of the first 100 threads: write "..." if there are
46-
more threads.
47-
48-
If current_tstate is NULL, the function tries to get the Python thread state
49-
of the current thread. It is not an error if the function is unable to get
50-
the current Python thread state.
51-
52-
If interp is NULL, the function tries to get the interpreter state from
53-
the current Python thread state, or from
54-
_PyGILState_GetInterpreterStateUnsafe() in last resort.
55-
56-
It is better to pass NULL to interp and current_tstate, the function tries
57-
different options to retrieve this information.
58-
59-
This function is signal safe. */
60-
61-
extern const char* _Py_DumpTracebackThreads(
62-
int fd,
63-
PyInterpreterState *interp,
64-
PyThreadState *current_tstate);
65-
6617
/* Write a Unicode object into the file descriptor fd. Encode the string to
6718
ASCII using the backslashreplace error handler.
6819

Include/traceback.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,50 @@ PyAPI_FUNC(int) PyTraceBack_Print(PyObject *, PyObject *);
1313
PyAPI_DATA(PyTypeObject) PyTraceBack_Type;
1414
#define PyTraceBack_Check(v) Py_IS_TYPE((v), &PyTraceBack_Type)
1515

16+
/* Write the Python traceback into the file 'fd'. For example:
17+
18+
Traceback (most recent call first):
19+
File "xxx", line xxx in <xxx>
20+
File "xxx", line xxx in <xxx>
21+
...
22+
File "xxx", line xxx in <xxx>
23+
24+
This function is written for debug purpose only, to dump the traceback in
25+
the worst case: after a segmentation fault, at fatal error, etc. That's why,
26+
it is very limited. Strings are truncated to 100 characters and encoded to
27+
ASCII with backslashreplace. It doesn't write the source code, only the
28+
function name, filename and line number of each frame. Write only the first
29+
100 frames: if the traceback is truncated, write the line " ...".
30+
31+
This function is signal safe. */
32+
PyAPI_FUNC(void) PyUnstable_DumpTraceback(int fd, PyThreadState *tstate);
33+
34+
/* Write the traceback of all threads into the file 'fd'. current_thread can be
35+
NULL.
36+
37+
Return NULL on success, or an error message on error.
38+
39+
This function is written for debug purpose only. It calls
40+
PyUnstable_DumpTraceback() for each thread, and so has the same limitations. It
41+
only writes the traceback of the first 100 threads: write "..." if there are
42+
more threads.
43+
44+
If current_tstate is NULL, the function tries to get the Python thread state
45+
of the current thread. It is not an error if the function is unable to get
46+
the current Python thread state.
47+
48+
If interp is NULL, the function tries to get the interpreter state from
49+
the current Python thread state, or from
50+
_PyGILState_GetInterpreterStateUnsafe() in last resort.
51+
52+
It is better to pass NULL to interp and current_tstate, the function tries
53+
different options to retrieve this information.
54+
55+
This function is signal safe. */
56+
PyAPI_FUNC(const char*) PyUnstable_DumpTracebackThreads(
57+
int fd,
58+
PyInterpreterState *interp,
59+
PyThreadState *current_tstate);
1660

1761
#ifndef Py_LIMITED_API
1862
# define Py_CPYTHON_TRACEBACK_H

Modules/faulthandler.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#include "pycore_runtime.h" // _Py_ID()
88
#include "pycore_signal.h" // Py_NSIG
99
#include "pycore_time.h" // _PyTime_FromSecondsObject()
10-
#include "pycore_traceback.h" // _Py_DumpTracebackThreads
10+
#include "pycore_traceback.h" // _Py_DumpStack()
1111
#ifdef HAVE_UNISTD_H
1212
# include <unistd.h> // _exit()
1313
#endif
@@ -205,14 +205,15 @@ faulthandler_dump_traceback(int fd, int all_threads,
205205
PyThreadState *tstate = PyGILState_GetThisThreadState();
206206

207207
if (all_threads == 1) {
208-
(void)_Py_DumpTracebackThreads(fd, NULL, tstate);
208+
(void)PyUnstable_DumpTracebackThreads(fd, NULL, tstate);
209209
}
210210
else {
211211
if (all_threads == FT_IGNORE_ALL_THREADS) {
212212
PUTS(fd, "<Cannot show all threads while the GIL is disabled>\n");
213213
}
214-
if (tstate != NULL)
215-
_Py_DumpTraceback(fd, tstate);
214+
if (tstate != NULL) {
215+
PyUnstable_DumpTraceback(fd, tstate);
216+
}
216217
}
217218

218219
reentrant = 0;
@@ -273,7 +274,7 @@ faulthandler_dump_traceback_py_impl(PyObject *module, PyObject *file,
273274
/* gh-128400: Accessing other thread states while they're running
274275
* isn't safe if those threads are running. */
275276
_PyEval_StopTheWorld(interp);
276-
errmsg = _Py_DumpTracebackThreads(fd, NULL, tstate);
277+
errmsg = PyUnstable_DumpTracebackThreads(fd, NULL, tstate);
277278
_PyEval_StartTheWorld(interp);
278279
if (errmsg != NULL) {
279280
PyErr_SetString(PyExc_RuntimeError, errmsg);
@@ -282,7 +283,7 @@ faulthandler_dump_traceback_py_impl(PyObject *module, PyObject *file,
282283
}
283284
}
284285
else {
285-
_Py_DumpTraceback(fd, tstate);
286+
PyUnstable_DumpTraceback(fd, tstate);
286287
}
287288
Py_XDECREF(file);
288289

@@ -703,7 +704,7 @@ faulthandler_thread(void *unused)
703704

704705
(void)_Py_write_noraise(thread.fd, thread.header, (int)thread.header_len);
705706

706-
errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, NULL);
707+
errmsg = PyUnstable_DumpTracebackThreads(thread.fd, thread.interp, NULL);
707708
ok = (errmsg == NULL);
708709

709710
if (thread.exit)

Python/pylifecycle.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
#include "pycore_setobject.h" // _PySet_NextEntry()
3030
#include "pycore_stats.h" // _PyStats_InterpInit()
3131
#include "pycore_sysmodule.h" // _PySys_ClearAttrString()
32-
#include "pycore_traceback.h" // _Py_DumpTracebackThreads()
32+
#include "pycore_traceback.h" // _Py_DumpTraceback_Init()
3333
#include "pycore_typeobject.h" // _PyTypes_InitTypes()
3434
#include "pycore_typevarobject.h" // _Py_clear_generic_types()
3535
#include "pycore_unicodeobject.h" // _PyUnicode_InitTypes()
@@ -3208,9 +3208,9 @@ _Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp,
32083208

32093209
/* display the current Python stack */
32103210
#ifndef Py_GIL_DISABLED
3211-
_Py_DumpTracebackThreads(fd, interp, tstate);
3211+
PyUnstable_DumpTracebackThreads(fd, interp, tstate);
32123212
#else
3213-
_Py_DumpTraceback(fd, tstate);
3213+
PyUnstable_DumpTraceback(fd, tstate);
32143214
#endif
32153215
}
32163216

Python/traceback.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,7 +1168,7 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
11681168
The caller is responsible to call PyErr_CheckSignals() to call Python signal
11691169
handlers if signals were received. */
11701170
void
1171-
_Py_DumpTraceback(int fd, PyThreadState *tstate)
1171+
PyUnstable_DumpTraceback(int fd, PyThreadState *tstate)
11721172
{
11731173
dump_traceback(fd, tstate, 1);
11741174
}
@@ -1264,11 +1264,11 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current)
12641264
The caller is responsible to call PyErr_CheckSignals() to call Python signal
12651265
handlers if signals were received. */
12661266
const char* _Py_NO_SANITIZE_THREAD
1267-
_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
1268-
PyThreadState *current_tstate)
1267+
PyUnstable_DumpTracebackThreads(int fd, PyInterpreterState *interp,
1268+
PyThreadState *current_tstate)
12691269
{
12701270
if (current_tstate == NULL) {
1271-
/* _Py_DumpTracebackThreads() is called from signal handlers by
1271+
/* PyUnstable_DumpTracebackThreads() is called from signal handlers by
12721272
faulthandler.
12731273
12741274
SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL are synchronous signals

Tools/wasm/emscripten/node_entry.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@ try {
5353
// Show JavaScript exception and traceback
5454
console.warn(e);
5555
// Show Python exception and traceback
56-
Module.__Py_DumpTraceback(2, Module._PyGILState_GetThisThreadState());
56+
Module.PyUnstable_DumpTraceback(2, Module._PyGILState_GetThisThreadState());
5757
process.exit(1);
5858
}

Tools/wasm/emscripten/web_example_pyrepl_jspi/src.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,6 @@ try {
189189
// Show JavaScript exception and traceback
190190
console.warn(e);
191191
// Show Python exception and traceback
192-
Module.__Py_DumpTraceback(2, Module._PyGILState_GetThisThreadState());
192+
Module.PyUnstable_DumpTraceback(2, Module._PyGILState_GetThisThreadState());
193193
process.exit(1);
194194
}

configure

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2357,7 +2357,7 @@ AS_CASE([$ac_sys_system],
23572357
dnl Include file system support
23582358
AS_VAR_APPEND([LINKFORSHARED], [" -sFORCE_FILESYSTEM -lidbfs.js -lnodefs.js -lproxyfs.js -lworkerfs.js"])
23592359
AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_RUNTIME_METHODS=FS,callMain,ENV,HEAPU32,TTY"])
2360-
AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__Py_DumpTraceback,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET"])
2360+
AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET"])
23612361
AS_VAR_APPEND([LINKFORSHARED], [" -sSTACK_SIZE=5MB"])
23622362
dnl Avoid bugs in JS fallback string decoding path
23632363
AS_VAR_APPEND([LINKFORSHARED], [" -sTEXTDECODER=2"])

0 commit comments

Comments
 (0)