From fc1d6adc6d762936f3f777dcd6683523a99c7067 Mon Sep 17 00:00:00 2001 From: Alex Malyshev Date: Sun, 5 Apr 2026 17:36:22 -0400 Subject: [PATCH 1/8] Add PyUnstable_DumpTraceback() and PyUnstable_DumpTracebackThreads() Public versions of _Py_DumpTraceback() and _Py_DumpTracebackThreads(). --- Doc/c-api/exceptions.rst | 64 +++++++++++++++++++ Doc/whatsnew/3.15.rst | 5 ++ Include/cpython/traceback.h | 7 ++ Include/internal/pycore_traceback.h | 49 -------------- Modules/faulthandler.c | 26 ++++---- Platforms/emscripten/node_entry.mjs | 2 +- .../web_example_pyrepl_jspi/src.mjs | 2 +- Python/pylifecycle.c | 6 +- Python/traceback.c | 27 +++++--- configure | 3 +- configure.ac | 2 +- 11 files changed, 115 insertions(+), 78 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 8ecd7c62517104..576b58e6fbcb19 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -1346,3 +1346,67 @@ Tracebacks This function returns ``0`` on success, and returns ``-1`` with an exception set on failure. + +.. c:function:: const char* PyUnstable_DumpTraceback(int fd, PyThreadState *tstate) + + Write a trace of the Python stack in *tstate* into the file *fd*. The format + looks like:: + + Traceback (most recent call first): + File "xxx", line xxx in + File "xxx", line xxx in + ... + File "xxx", line xxx in + + This function is meant to debug situations such as segfaults, fatal errors, + and similar. The file and function names it outputs are encoded to ASCII with + backslashreplace and truncated to 500 characters. It writes only the first + 100 frames; further frames are truncated with the line ``...``. + + This function will return ``NULL`` on success, or an error message on error. + + This function is intended for use in crash scenarios such as signal handlers + for SIGSEGV, where the interpreter may be in an inconsistent state. Given + that it reads interpreter data structures that may be partially modified, the + function might produce incomplete output or it may even crash itself. + + The caller does not need to hold an :term:`attached thread state`, nor does + *tstate* need to be attached. + + .. versionadded:: next + +.. c:function:: const char* PyUnstable_DumpTracebackThreads(int fd, PyInterpreterState *interp, PyThreadState *current_tstate) + + Write the traces of all Python threads in *interp* into the file *fd*. + + If *interp* is ``NULL`` then this function will try to identify the current + interpreter using thread-specific storage. If it cannot, it will return an + error. + + If *current_tstate* is not ``NULL`` then it will be used to identify what the + current thread is in the written output. If it is ``NULL`` then this function + will identify the current thread using thread-specific storage. It is not an + error if the function is unable to get the current Python thread state. + + This function will return ``NULL`` on success, or an error message on error. + It will also write this error message to *fd*. + + This function is meant to debug debug situations such as segfaults, fatal + errors, and similar. It calls :c:func:`PyUnstable_DumpTraceback` for each + thread. It only writes the tracebacks of the first 100 threads, further + output is truncated with the line ``...``. + + This function is intended for use in crash scenarios such as signal handlers + for SIGSEGV, where the interpreter may be in an inconsistent state. Given + that it reads interpreter data structures that may be partially modified, the + function might produce incomplete output or it may even crash itself. + + The caller does not need to hold an :term:`attached thread state`, nor does + *current_tstate* need to be attached. + + .. warning:: + On the :term:`free-threaded build`, this function is not thread-safe. If + another thread deletes its :term:`thread state` while this function is being + called, the process will likely crash. + + .. versionadded:: next diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index d1d4b92bcf4e97..54679c60fc9370 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1788,6 +1788,11 @@ New features Python 3.14. (Contributed by Victor Stinner in :gh:`142417`.) +* Add :c:func:`PyUnstable_DumpTraceback` and + :c:func:`PyUnstable_DumpTracebackThreads` functions to safely output Python + stacktraces. + (Contributed by Alex Malyshev in :gh:`145559`.) + Changed C APIs -------------- diff --git a/Include/cpython/traceback.h b/Include/cpython/traceback.h index 81c51944f136f2..c6a6f3814a2417 100644 --- a/Include/cpython/traceback.h +++ b/Include/cpython/traceback.h @@ -11,3 +11,10 @@ struct _traceback { int tb_lasti; int tb_lineno; }; + +PyAPI_FUNC(const char*) PyUnstable_DumpTraceback(int fd, PyThreadState *tstate); + +PyAPI_FUNC(const char*) PyUnstable_DumpTracebackThreads( + int fd, + PyInterpreterState *interp, + PyThreadState *current_tstate); diff --git a/Include/internal/pycore_traceback.h b/Include/internal/pycore_traceback.h index 8357cce9d899fb..e7729965b71769 100644 --- a/Include/internal/pycore_traceback.h +++ b/Include/internal/pycore_traceback.h @@ -14,55 +14,6 @@ PyAPI_FUNC(int) _Py_DisplaySourceLine(PyObject *, PyObject *, int, int, int *, P // Export for 'pyexact' shared extension PyAPI_FUNC(void) _PyTraceback_Add(const char *, const char *, int); -/* Write the Python traceback into the file 'fd'. For example: - - Traceback (most recent call first): - File "xxx", line xxx in - File "xxx", line xxx in - ... - File "xxx", line xxx in - - This function is written for debug purpose only, to dump the traceback in - the worst case: after a segmentation fault, at fatal error, etc. That's why, - it is very limited. Strings are truncated to 100 characters and encoded to - ASCII with backslashreplace. It doesn't write the source code, only the - function name, filename and line number of each frame. Write only the first - 100 frames: if the traceback is truncated, write the line " ...". - - This function is signal safe. */ - -extern void _Py_DumpTraceback( - int fd, - PyThreadState *tstate); - -/* Write the traceback of all threads into the file 'fd'. current_thread can be - NULL. - - Return NULL on success, or an error message on error. - - This function is written for debug purpose only. It calls - _Py_DumpTraceback() for each thread, and so has the same limitations. It - only write the traceback of the first 100 threads: write "..." if there are - more threads. - - If current_tstate is NULL, the function tries to get the Python thread state - of the current thread. It is not an error if the function is unable to get - the current Python thread state. - - If interp is NULL, the function tries to get the interpreter state from - the current Python thread state, or from - _PyGILState_GetInterpreterStateUnsafe() in last resort. - - It is better to pass NULL to interp and current_tstate, the function tries - different options to retrieve this information. - - This function is signal safe. */ - -extern const char* _Py_DumpTracebackThreads( - int fd, - PyInterpreterState *interp, - PyThreadState *current_tstate); - /* Write a Unicode object into the file descriptor fd. Encode the string to ASCII using the backslashreplace error handler. diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index bc7731c2588dc0..437575ea6b793f 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -7,7 +7,7 @@ #include "pycore_runtime.h" // _Py_ID() #include "pycore_signal.h" // Py_NSIG #include "pycore_time.h" // _PyTime_FromSecondsObject() -#include "pycore_traceback.h" // _Py_DumpTracebackThreads +#include "pycore_traceback.h" // _Py_DumpStack() #ifdef HAVE_UNISTD_H # include // _exit() #endif @@ -205,14 +205,15 @@ faulthandler_dump_traceback(int fd, int all_threads, PyThreadState *tstate = PyGILState_GetThisThreadState(); if (all_threads == 1) { - (void)_Py_DumpTracebackThreads(fd, NULL, tstate); + (void)PyUnstable_DumpTracebackThreads(fd, NULL, tstate); } else { if (all_threads == FT_IGNORE_ALL_THREADS) { PUTS(fd, "\n"); } - if (tstate != NULL) - _Py_DumpTraceback(fd, tstate); + if (tstate != NULL) { + PyUnstable_DumpTraceback(fd, tstate); + } } reentrant = 0; @@ -273,17 +274,18 @@ faulthandler_dump_traceback_py_impl(PyObject *module, PyObject *file, /* gh-128400: Accessing other thread states while they're running * isn't safe if those threads are running. */ _PyEval_StopTheWorld(interp); - errmsg = _Py_DumpTracebackThreads(fd, NULL, tstate); + errmsg = PyUnstable_DumpTracebackThreads(fd, NULL, tstate); _PyEval_StartTheWorld(interp); - if (errmsg != NULL) { - PyErr_SetString(PyExc_RuntimeError, errmsg); - Py_XDECREF(file); - return NULL; - } } else { - _Py_DumpTraceback(fd, tstate); + errmsg = PyUnstable_DumpTraceback(fd, tstate); + } + if (errmsg != NULL) { + PyErr_SetString(PyExc_RuntimeError, errmsg); + Py_XDECREF(file); + return NULL; } + Py_XDECREF(file); if (PyErr_CheckSignals()) @@ -703,7 +705,7 @@ faulthandler_thread(void *unused) (void)_Py_write_noraise(thread.fd, thread.header, (int)thread.header_len); - errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, NULL); + errmsg = PyUnstable_DumpTracebackThreads(thread.fd, thread.interp, NULL); ok = (errmsg == NULL); if (thread.exit) diff --git a/Platforms/emscripten/node_entry.mjs b/Platforms/emscripten/node_entry.mjs index 9478b7714adbc8..110aadc5de1014 100644 --- a/Platforms/emscripten/node_entry.mjs +++ b/Platforms/emscripten/node_entry.mjs @@ -57,6 +57,6 @@ try { // Show JavaScript exception and traceback console.warn(e); // Show Python exception and traceback - Module.__Py_DumpTraceback(2, Module._PyGILState_GetThisThreadState()); + Module.PyUnstable_DumpTraceback(2, Module._PyGILState_GetThisThreadState()); process.exit(1); } diff --git a/Platforms/emscripten/web_example_pyrepl_jspi/src.mjs b/Platforms/emscripten/web_example_pyrepl_jspi/src.mjs index 5642372c9d2472..38a622117c2a50 100644 --- a/Platforms/emscripten/web_example_pyrepl_jspi/src.mjs +++ b/Platforms/emscripten/web_example_pyrepl_jspi/src.mjs @@ -189,6 +189,6 @@ try { // Show JavaScript exception and traceback console.warn(e); // Show Python exception and traceback - Module.__Py_DumpTraceback(2, Module._PyGILState_GetThisThreadState()); + Module.PyUnstable_DumpTraceback(2, Module._PyGILState_GetThisThreadState()); process.exit(1); } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 5da0f3e5be3a70..539a262039e16c 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -29,7 +29,7 @@ #include "pycore_setobject.h" // _PySet_NextEntry() #include "pycore_stats.h" // _PyStats_InterpInit() #include "pycore_sysmodule.h" // _PySys_ClearAttrString() -#include "pycore_traceback.h" // _Py_DumpTracebackThreads() +#include "pycore_traceback.h" // PyUnstable_TracebackThreads() #include "pycore_tuple.h" // _PyTuple_FromPair #include "pycore_typeobject.h" // _PyTypes_InitTypes() #include "pycore_typevarobject.h" // _Py_clear_generic_types() @@ -3261,9 +3261,9 @@ _Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp, /* display the current Python stack */ #ifndef Py_GIL_DISABLED - _Py_DumpTracebackThreads(fd, interp, tstate); + PyUnstable_DumpTracebackThreads(fd, interp, tstate); #else - _Py_DumpTraceback(fd, tstate); + PyUnstable_DumpTraceback(fd, tstate); #endif } diff --git a/Python/traceback.c b/Python/traceback.c index 1e8c9c879f9aac..efdbb0e1af4c9e 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1167,10 +1167,11 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header) The caller is responsible to call PyErr_CheckSignals() to call Python signal handlers if signals were received. */ -void -_Py_DumpTraceback(int fd, PyThreadState *tstate) +const char* +PyUnstable_DumpTraceback(int fd, PyThreadState *tstate) { dump_traceback(fd, tstate, 1); + return NULL; } #if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) @@ -1257,6 +1258,14 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current) PUTS(fd, " (most recent call first):\n"); } +/* Write an error string and also return it at the same time. */ +static const char* +dump_error(int fd, const char *msg) +{ + PUTS(fd, msg); + return msg; +} + /* Dump the traceback of all Python threads into fd. Use write() to write the traceback and retry if write() is interrupted by a signal (failed with EINTR), but don't call the Python signal handler. @@ -1264,11 +1273,11 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current) The caller is responsible to call PyErr_CheckSignals() to call Python signal handlers if signals were received. */ const char* _Py_NO_SANITIZE_THREAD -_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, - PyThreadState *current_tstate) +PyUnstable_DumpTracebackThreads(int fd, PyInterpreterState *interp, + PyThreadState *current_tstate) { if (current_tstate == NULL) { - /* _Py_DumpTracebackThreads() is called from signal handlers by + /* PyUnstable_DumpTracebackThreads() is called from signal handlers by faulthandler. SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL are synchronous signals @@ -1283,7 +1292,7 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, } if (current_tstate != NULL && tstate_is_freed(current_tstate)) { - return "tstate is freed"; + return dump_error(fd, "tstate is freed"); } if (interp == NULL) { @@ -1291,7 +1300,7 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, interp = _PyGILState_GetInterpreterStateUnsafe(); if (interp == NULL) { /* We need the interpreter state to get Python threads */ - return "unable to get the interpreter state"; + return dump_error(fd, "unable to get the interpreter state"); } } else { @@ -1301,13 +1310,13 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, assert(interp != NULL); if (interp_is_freed(interp)) { - return "interp is freed"; + return dump_error(fd, "interp is freed"); } /* Get the current interpreter from the current thread */ PyThreadState *tstate = PyInterpreterState_ThreadHead(interp); if (tstate == NULL) - return "unable to get the thread head state"; + return dump_error(fd, "unable to get the thread head state"); /* Dump the traceback of each thread */ unsigned int nthreads = 0; diff --git a/configure b/configure index 4726b4fe3102ac..1d070504d75021 100755 --- a/configure +++ b/configure @@ -9699,7 +9699,7 @@ fi as_fn_append LINKFORSHARED " -sFORCE_FILESYSTEM -lidbfs.js -lnodefs.js -lproxyfs.js -lworkerfs.js" as_fn_append LINKFORSHARED " -sEXPORTED_RUNTIME_METHODS=FS,callMain,ENV,HEAPU32,TTY,ERRNO_CODES" - as_fn_append LINKFORSHARED " -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__Py_DumpTraceback,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET" + as_fn_append LINKFORSHARED " -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET" as_fn_append LINKFORSHARED " -sSTACK_SIZE=5MB" as_fn_append LINKFORSHARED " -sTEXTDECODER=2" @@ -36311,4 +36311,3 @@ if test "$ac_cv_header_stdatomic_h" != "yes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Your compiler or platform does have a working C11 stdatomic.h. A future version of Python may require stdatomic.h." >&5 printf "%s\n" "$as_me: Your compiler or platform does have a working C11 stdatomic.h. A future version of Python may require stdatomic.h." >&6;} fi - diff --git a/configure.ac b/configure.ac index dd860292cc2058..44754fc0353600 100644 --- a/configure.ac +++ b/configure.ac @@ -2366,7 +2366,7 @@ AS_CASE([$ac_sys_system], dnl Include file system support AS_VAR_APPEND([LINKFORSHARED], [" -sFORCE_FILESYSTEM -lidbfs.js -lnodefs.js -lproxyfs.js -lworkerfs.js"]) AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_RUNTIME_METHODS=FS,callMain,ENV,HEAPU32,TTY,ERRNO_CODES"]) - AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__Py_DumpTraceback,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET"]) + AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET"]) AS_VAR_APPEND([LINKFORSHARED], [" -sSTACK_SIZE=5MB"]) dnl Avoid bugs in JS fallback string decoding path AS_VAR_APPEND([LINKFORSHARED], [" -sTEXTDECODER=2"]) From d7c02c5fd113b3e62a6ebdf0194bc1bb0d2a1e2f Mon Sep 17 00:00:00 2001 From: Alex Malyshev Date: Sun, 5 Apr 2026 18:19:18 -0400 Subject: [PATCH 2/8] Generate NEWS.d entry with blurb --- .../next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst diff --git a/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst b/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst new file mode 100644 index 00000000000000..e2e035d8ec142a --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst @@ -0,0 +1,2 @@ +Rename `_Py_DumpTraceback` and `_Py_DumpTracebackThreads` to +`PyUnstable_DumpTraceback` and `PyUnstable_DumpTracebackThreads`. From d7d4720d70b574686f9ed514407c056dd2289b35 Mon Sep 17 00:00:00 2001 From: Alex Malyshev Date: Sun, 5 Apr 2026 18:21:37 -0400 Subject: [PATCH 3/8] Run `make regen-configure` --- configure | 1 + 1 file changed, 1 insertion(+) diff --git a/configure b/configure index 1d070504d75021..7c056315dbc532 100755 --- a/configure +++ b/configure @@ -36311,3 +36311,4 @@ if test "$ac_cv_header_stdatomic_h" != "yes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Your compiler or platform does have a working C11 stdatomic.h. A future version of Python may require stdatomic.h." >&5 printf "%s\n" "$as_me: Your compiler or platform does have a working C11 stdatomic.h. A future version of Python may require stdatomic.h." >&6;} fi + From 7809d9ce3de96c34187c6d203daf3858f7f90e96 Mon Sep 17 00:00:00 2001 From: Alex Malyshev Date: Sun, 5 Apr 2026 18:26:21 -0400 Subject: [PATCH 4/8] Use backticks and roles properly in NEWS.d entry --- .../next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst b/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst index e2e035d8ec142a..0165f5642b9c40 100644 --- a/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst +++ b/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst @@ -1,2 +1,2 @@ -Rename `_Py_DumpTraceback` and `_Py_DumpTracebackThreads` to -`PyUnstable_DumpTraceback` and `PyUnstable_DumpTracebackThreads`. +Rename ``_Py_DumpTraceback`` and ``_Py_DumpTracebackThreads`` to +c:func:`PyUnstable_DumpTraceback` and c:func`PyUnstable_DumpTracebackThreads`. From 0e0329280d8929399880852fdc27eed8c67c2112 Mon Sep 17 00:00:00 2001 From: Alex Malyshev Date: Sun, 5 Apr 2026 18:29:15 -0400 Subject: [PATCH 5/8] Fix missing colon --- .../next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst b/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst index 0165f5642b9c40..dd2a556c171f1f 100644 --- a/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst +++ b/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst @@ -1,2 +1,2 @@ Rename ``_Py_DumpTraceback`` and ``_Py_DumpTracebackThreads`` to -c:func:`PyUnstable_DumpTraceback` and c:func`PyUnstable_DumpTracebackThreads`. +c:func:`PyUnstable_DumpTraceback` and c:func:`PyUnstable_DumpTracebackThreads`. From 4bcd445a97ce20a845acb42368adfdff1393e1aa Mon Sep 17 00:00:00 2001 From: Alex Malyshev Date: Sun, 5 Apr 2026 18:32:25 -0400 Subject: [PATCH 6/8] More sphinx-lint fixes --- .../next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst b/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst index dd2a556c171f1f..9495d42160a9cd 100644 --- a/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst +++ b/Misc/NEWS.d/next/C_API/2026-04-05-18-18-59.gh-issue-145559.qKJH9S.rst @@ -1,2 +1,3 @@ Rename ``_Py_DumpTraceback`` and ``_Py_DumpTracebackThreads`` to -c:func:`PyUnstable_DumpTraceback` and c:func:`PyUnstable_DumpTracebackThreads`. +:c:func:`PyUnstable_DumpTraceback` and +:c:func:`PyUnstable_DumpTracebackThreads`. From 5448a45c88b325f418b785452d3a6a19152dbc3d Mon Sep 17 00:00:00 2001 From: Alex Malyshev Date: Thu, 30 Apr 2026 23:29:59 -0400 Subject: [PATCH 7/8] Add back max_threads argument to decl after merge --- Include/cpython/traceback.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Include/cpython/traceback.h b/Include/cpython/traceback.h index c6a6f3814a2417..7f42730f1b0919 100644 --- a/Include/cpython/traceback.h +++ b/Include/cpython/traceback.h @@ -17,4 +17,5 @@ PyAPI_FUNC(const char*) PyUnstable_DumpTraceback(int fd, PyThreadState *tstate); PyAPI_FUNC(const char*) PyUnstable_DumpTracebackThreads( int fd, PyInterpreterState *interp, - PyThreadState *current_tstate); + PyThreadState *current_tstate, + Py_ssize_t max_threads); From 1ec0367405409f5eaab482b99e8103dfc1f61be7 Mon Sep 17 00:00:00 2001 From: Alex Malyshev Date: Thu, 30 Apr 2026 23:33:56 -0400 Subject: [PATCH 8/8] Document max_threads argument --- Doc/c-api/exceptions.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 28ffd78aa1fa42..f30bbdfee4a60f 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -1377,7 +1377,7 @@ Tracebacks .. versionadded:: next -.. c:function:: const char* PyUnstable_DumpTracebackThreads(int fd, PyInterpreterState *interp, PyThreadState *current_tstate) +.. c:function:: const char* PyUnstable_DumpTracebackThreads(int fd, PyInterpreterState *interp, PyThreadState *current_tstate, Py_ssize_t max_threads) Write the traces of all Python threads in *interp* into the file *fd*. @@ -1395,8 +1395,9 @@ Tracebacks This function is meant to debug debug situations such as segfaults, fatal errors, and similar. It calls :c:func:`PyUnstable_DumpTraceback` for each - thread. It only writes the tracebacks of the first 100 threads, further - output is truncated with the line ``...``. + thread. It only writes the tracebacks of the first *max_threads* threads, + further output is truncated with the line ``...``. If *max_threads* is 0, the + function will use a default value of 100 for the argument. This function is intended for use in crash scenarios such as signal handlers for SIGSEGV, where the interpreter may be in an inconsistent state. Given