* Each string is limited to 500 characters.
* Only the filename, the function name and the line number are
displayed. (no source code)
-* It is limited to 100 frames and 100 threads.
+* It is limited to 100 frames per thread, and 100 threads
+ (configurable via *max_threads*).
* The order is reversed: the most recent call is shown first.
By default, the Python traceback is written to :data:`sys.stderr`. To see
Dumping the traceback
---------------------
-.. function:: dump_traceback(file=sys.stderr, all_threads=True)
+.. function:: dump_traceback(file=sys.stderr, all_threads=True, *, max_threads=100)
Dump the tracebacks of all threads into *file*. If *all_threads* is
- ``False``, dump only the current thread.
+ ``False``, dump only the current thread. *max_threads* caps the number
+ of threads dumped.
.. seealso:: :func:`traceback.print_tb`, which can be used to print a traceback object.
.. versionchanged:: 3.5
Added support for passing file descriptor to this function.
+ .. versionchanged:: next
+ Added the *max_threads* keyword argument.
+
Dumping the C stack
-------------------
Fault handler state
-------------------
-.. function:: enable(file=sys.stderr, all_threads=True, c_stack=True)
+.. function:: enable(file=sys.stderr, all_threads=True, c_stack=True, *, max_threads=100)
Enable the fault handler: install handlers for the :const:`~signal.SIGSEGV`,
:const:`~signal.SIGFPE`, :const:`~signal.SIGABRT`, :const:`~signal.SIGBUS`
traceback, unless the system does not support it. See :func:`dump_c_stack` for
more information on compatibility.
+ *max_threads* caps the number of threads dumped when a fatal signal fires.
+
.. versionchanged:: 3.5
Added support for passing file descriptor to this function.
.. versionchanged:: 3.14
The dump now displays the C stack trace if *c_stack* is true.
+ .. versionchanged:: next
+ Added the *max_threads* keyword argument.
+
.. function:: disable()
Disable the fault handler: uninstall the signal handlers installed by
Dumping the tracebacks after a timeout
--------------------------------------
-.. function:: dump_traceback_later(timeout, repeat=False, file=sys.stderr, exit=False)
+.. function:: dump_traceback_later(timeout, repeat=False, file=sys.stderr, exit=False, *, max_threads=100)
Dump the tracebacks of all threads, after a timeout of *timeout* seconds, or
every *timeout* seconds if *repeat* is ``True``. If *exit* is ``True``, call
:c:func:`!_exit` exits the process immediately, which means it doesn't do any
cleanup like flushing file buffers.) If the function is called twice, the new
call replaces previous parameters and resets the timeout. The timer has a
- sub-second resolution.
+ sub-second resolution. *max_threads* caps the number of threads dumped.
The *file* must be kept open until the traceback is dumped or
:func:`cancel_dump_traceback_later` is called: see :ref:`issue with file
.. versionchanged:: 3.7
This function is now always available.
+ .. versionchanged:: next
+ Added the *max_threads* keyword argument.
+
.. function:: cancel_dump_traceback_later()
Cancel the last call to :func:`dump_traceback_later`.
Dumping the traceback on a user signal
--------------------------------------
-.. function:: register(signum, file=sys.stderr, all_threads=True, chain=False)
+.. function:: register(signum, file=sys.stderr, all_threads=True, chain=False, *, max_threads=100)
Register a user signal: install a handler for the *signum* signal to dump
the traceback of all threads, or of the current thread if *all_threads* is
``False``, into *file*. Call the previous handler if chain is ``True``.
+ *max_threads* caps the number of threads dumped.
The *file* must be kept open until the signal is unregistered by
:func:`unregister`: see :ref:`issue with file descriptors <faulthandler-fd>`.
.. versionchanged:: 3.5
Added support for passing file descriptor to this function.
+ .. versionchanged:: next
+ Added the *max_threads* keyword argument.
+
.. function:: unregister(signum)
Unregister a user signal: uninstall the handler of the *signum* signal
(Contributed by Jiahao Li in :gh:`134580`.)
+faulthandler
+------------
+
+* Added the *max_threads* parameter in :func:`faulthandler.enable`,
+ :func:`faulthandler.dump_traceback`, :func:`faulthandler.dump_traceback_later`,
+ and :func:`faulthandler.register`.
+ (Contributed by Eric Froemling in :gh:`149085`.)
+
+
functools
---------
int chain;
_Py_sighandler_t previous;
PyInterpreterState *interp;
+ Py_ssize_t max_threads;
};
#endif /* FAULTHANDLER_USER */
void *exc_handler;
#endif
int c_stack;
+ Py_ssize_t max_threads;
} fatal_error;
struct {
int exit;
char *header;
size_t header_len;
+ Py_ssize_t max_threads;
/* The main thread always holds this lock. It is only released when
faulthandler_thread() is interrupted before this thread exits, or at
Python exit. */
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mask));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(match));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(max_length));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(max_threads));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxdigits));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxevents));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxlen));
STRUCT_FOR_ID(mask)
STRUCT_FOR_ID(match)
STRUCT_FOR_ID(max_length)
+ STRUCT_FOR_ID(max_threads)
STRUCT_FOR_ID(maxdigits)
STRUCT_FOR_ID(maxevents)
STRUCT_FOR_ID(maxlen)
INIT_ID(mask), \
INIT_ID(match), \
INIT_ID(max_length), \
+ INIT_ID(max_threads), \
INIT_ID(maxdigits), \
INIT_ID(maxevents), \
INIT_ID(maxlen), \
extern const char* _Py_DumpTracebackThreads(
int fd,
PyInterpreterState *interp,
- PyThreadState *current_tstate);
+ PyThreadState *current_tstate,
+ Py_ssize_t max_threads);
/* Write a Unicode object into the file descriptor fd. Encode the string to
ASCII using the backslashreplace error handler.
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(max_threads);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(maxdigits);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
def test_dump_traceback_later_twice(self):
self.check_dump_traceback_later(loops=2)
+ def test_dump_traceback_max_threads(self):
+ # max_threads caps the dump and writes "...\n" when truncated.
+ # Spawn N worker threads, dump with cap < N, and verify the
+ # marker is present and exactly CAP thread headers are written.
+ code = dedent("""
+ import faulthandler
+ import sys
+ import threading
+
+ NTHREADS = 6
+ CAP = 3
+
+ ready = threading.Barrier(NTHREADS + 1)
+ stop = threading.Event()
+
+ def worker():
+ ready.wait()
+ stop.wait()
+
+ threads = [threading.Thread(target=worker) for _ in range(NTHREADS)]
+ for t in threads:
+ t.start()
+ ready.wait()
+ try:
+ faulthandler.dump_traceback(file=sys.stderr, max_threads=CAP)
+ finally:
+ stop.set()
+ for t in threads:
+ t.join()
+ """).strip()
+ proc = script_helper.assert_python_ok('-c', code)
+ output = proc.err
+ # Truncation marker is written on its own line when the cap is hit.
+ self.assertIn(b"\n...\n", output)
+ # Cap of 3 means exactly 3 thread headers in the dump.
+ self.assertEqual(output.count(b"Thread 0x"), 3)
+
+ @skip_segfault_on_android
+ @unittest.skipIf(support.Py_GIL_DISABLED,
+ "fatal-signal handler only dumps the current thread "
+ "when the GIL is disabled")
+ def test_enable_max_threads(self):
+ # enable(max_threads=N) caps the thread dump produced when a
+ # fatal signal fires.
+ code = dedent("""
+ import faulthandler
+ import threading
+
+ NTHREADS = 6
+ CAP = 3
+
+ ready = threading.Barrier(NTHREADS + 1)
+ stop = threading.Event()
+
+ def worker():
+ ready.wait()
+ stop.wait()
+
+ for _ in range(NTHREADS):
+ threading.Thread(target=worker, daemon=True).start()
+ ready.wait()
+ faulthandler.enable(max_threads=CAP)
+ faulthandler._sigsegv()
+ """).strip()
+ output, exitcode = self.get_output(code)
+ output = '\n'.join(output)
+ # Cap of 3 means the dump is truncated with "..." on its own line.
+ self.assertIn("\n...\n", output)
+ self.assertNotEqual(exitcode, 0)
+
@unittest.skipIf(not hasattr(faulthandler, "register"),
"need faulthandler.register")
def check_register(self, filename=False, all_threads=False,
def test_register_chain(self):
self.check_register(chain=True)
+ @unittest.skipIf(not hasattr(faulthandler, "register"),
+ "need faulthandler.register")
+ def test_register_max_threads(self):
+ # register(max_threads=N) caps the thread dump produced when
+ # the registered signal fires.
+ code = dedent("""
+ import faulthandler
+ import signal
+ import threading
+
+ NTHREADS = 6
+ CAP = 3
+
+ ready = threading.Barrier(NTHREADS + 1)
+ stop = threading.Event()
+
+ def worker():
+ ready.wait()
+ stop.wait()
+
+ threads = [threading.Thread(target=worker) for _ in range(NTHREADS)]
+ for t in threads:
+ t.start()
+ ready.wait()
+ try:
+ faulthandler.register(signal.SIGUSR1, all_threads=True,
+ max_threads=CAP)
+ signal.raise_signal(signal.SIGUSR1)
+ finally:
+ stop.set()
+ for t in threads:
+ t.join()
+ """).strip()
+ proc = script_helper.assert_python_ok('-c', code)
+ output = proc.err
+ # Cap of 3 means the dump is truncated with "..." on its own line.
+ self.assertIn(b"\n...\n", output)
+ # Cap of 3 means exactly 3 thread headers in the dump.
+ self.assertEqual(output.count(b"Thread 0x"), 3)
+
@contextmanager
def check_stderr_none(self):
stderr = sys.stderr
--- /dev/null
+Add a *max_threads* keyword argument to :func:`faulthandler.dump_traceback`,
+:func:`faulthandler.dump_traceback_later`, :func:`faulthandler.enable`, and
+:func:`faulthandler.register`.
# include "pycore_gc.h" // PyGC_Head
# include "pycore_runtime.h" // _Py_ID()
#endif
+#include "pycore_abstract.h" // _PyNumber_Index()
#include "pycore_long.h" // _PyLong_UnsignedInt_Converter()
#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
PyDoc_STRVAR(faulthandler_dump_traceback_py__doc__,
-"dump_traceback($module, /, file=sys.stderr, all_threads=True)\n"
+"dump_traceback($module, /, file=sys.stderr, all_threads=True, *,\n"
+" max_threads=100)\n"
"--\n"
"\n"
"Dump the traceback of the current thread into file.\n"
"\n"
-"Dump the traceback of all threads if all_threads is true.");
+"Dump the traceback of all threads if all_threads is true. max_threads\n"
+"caps the number of threads dumped.");
#define FAULTHANDLER_DUMP_TRACEBACK_PY_METHODDEF \
{"dump_traceback", _PyCFunction_CAST(faulthandler_dump_traceback_py), METH_FASTCALL|METH_KEYWORDS, faulthandler_dump_traceback_py__doc__},
static PyObject *
faulthandler_dump_traceback_py_impl(PyObject *module, PyObject *file,
- int all_threads);
+ int all_threads, Py_ssize_t max_threads);
static PyObject *
faulthandler_dump_traceback_py(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(file), &_Py_ID(all_threads), },
+ .ob_item = { &_Py_ID(file), &_Py_ID(all_threads), &_Py_ID(max_threads), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"file", "all_threads", NULL};
+ static const char * const _keywords[] = {"file", "all_threads", "max_threads", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "dump_traceback",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
PyObject *file = NULL;
int all_threads = 1;
+ Py_ssize_t max_threads = 100;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
goto skip_optional_pos;
}
}
- all_threads = PyObject_IsTrue(args[1]);
- if (all_threads < 0) {
- goto exit;
+ if (args[1]) {
+ all_threads = PyObject_IsTrue(args[1]);
+ if (all_threads < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
}
skip_optional_pos:
- return_value = faulthandler_dump_traceback_py_impl(module, file, all_threads);
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[2]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ max_threads = ival;
+ }
+skip_optional_kwonly:
+ return_value = faulthandler_dump_traceback_py_impl(module, file, all_threads, max_threads);
exit:
return return_value;
}
PyDoc_STRVAR(faulthandler_py_enable__doc__,
-"enable($module, /, file=sys.stderr, all_threads=True, c_stack=True)\n"
+"enable($module, /, file=sys.stderr, all_threads=True, c_stack=True, *,\n"
+" max_threads=100)\n"
"--\n"
"\n"
"Enable the fault handler.");
static PyObject *
faulthandler_py_enable_impl(PyObject *module, PyObject *file,
- int all_threads, int c_stack);
+ int all_threads, int c_stack,
+ Py_ssize_t max_threads);
static PyObject *
faulthandler_py_enable(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 3
+ #define NUM_KEYWORDS 4
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(file), &_Py_ID(all_threads), &_Py_ID(c_stack), },
+ .ob_item = { &_Py_ID(file), &_Py_ID(all_threads), &_Py_ID(c_stack), &_Py_ID(max_threads), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"file", "all_threads", "c_stack", NULL};
+ static const char * const _keywords[] = {"file", "all_threads", "c_stack", "max_threads", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "enable",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[3];
+ PyObject *argsbuf[4];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
PyObject *file = NULL;
int all_threads = 1;
int c_stack = 1;
+ Py_ssize_t max_threads = 100;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
goto skip_optional_pos;
}
}
- c_stack = PyObject_IsTrue(args[2]);
- if (c_stack < 0) {
- goto exit;
+ if (args[2]) {
+ c_stack = PyObject_IsTrue(args[2]);
+ if (c_stack < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
}
skip_optional_pos:
- return_value = faulthandler_py_enable_impl(module, file, all_threads, c_stack);
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[3]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ max_threads = ival;
+ }
+skip_optional_kwonly:
+ return_value = faulthandler_py_enable_impl(module, file, all_threads, c_stack, max_threads);
exit:
return return_value;
PyDoc_STRVAR(faulthandler_dump_traceback_later__doc__,
"dump_traceback_later($module, /, timeout, repeat=False,\n"
-" file=sys.stderr, exit=False)\n"
+" file=sys.stderr, exit=False, *, max_threads=100)\n"
"--\n"
"\n"
"Dump the traceback of all threads in timeout seconds.\n"
"\n"
"If repeat is true, the tracebacks of all threads are dumped every timeout\n"
-"seconds. If exit is true, call _exit(1) which is not safe.");
+"seconds. If exit is true, call _exit(1) which is not safe. max_threads\n"
+"caps the number of threads dumped.");
#define FAULTHANDLER_DUMP_TRACEBACK_LATER_METHODDEF \
{"dump_traceback_later", _PyCFunction_CAST(faulthandler_dump_traceback_later), METH_FASTCALL|METH_KEYWORDS, faulthandler_dump_traceback_later__doc__},
static PyObject *
faulthandler_dump_traceback_later_impl(PyObject *module,
PyObject *timeout_obj, int repeat,
- PyObject *file, int exit);
+ PyObject *file, int exit,
+ Py_ssize_t max_threads);
static PyObject *
faulthandler_dump_traceback_later(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 4
+ #define NUM_KEYWORDS 5
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(timeout), &_Py_ID(repeat), &_Py_ID(file), &_Py_ID(exit), },
+ .ob_item = { &_Py_ID(timeout), &_Py_ID(repeat), &_Py_ID(file), &_Py_ID(exit), &_Py_ID(max_threads), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"timeout", "repeat", "file", "exit", NULL};
+ static const char * const _keywords[] = {"timeout", "repeat", "file", "exit", "max_threads", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "dump_traceback_later",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[4];
+ PyObject *argsbuf[5];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
PyObject *timeout_obj;
int repeat = 0;
PyObject *file = NULL;
int exit = 0;
+ Py_ssize_t max_threads = 100;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 1, /*maxpos*/ 4, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
goto skip_optional_pos;
}
}
- exit = PyObject_IsTrue(args[3]);
- if (exit < 0) {
- goto exit;
+ if (args[3]) {
+ exit = PyObject_IsTrue(args[3]);
+ if (exit < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
}
skip_optional_pos:
- return_value = faulthandler_dump_traceback_later_impl(module, timeout_obj, repeat, file, exit);
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[4]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ max_threads = ival;
+ }
+skip_optional_kwonly:
+ return_value = faulthandler_dump_traceback_later_impl(module, timeout_obj, repeat, file, exit, max_threads);
exit:
return return_value;
PyDoc_STRVAR(faulthandler_register_py__doc__,
"register($module, /, signum, file=sys.stderr, all_threads=True,\n"
-" chain=False)\n"
+" chain=False, *, max_threads=100)\n"
"--\n"
"\n"
"Register a handler for the signal \'signum\'.\n"
"\n"
"Dump the traceback of the current thread, or of all threads if\n"
-"all_threads is True, into file.");
+"all_threads is True, into file. max_threads caps the number of threads\n"
+"dumped.");
#define FAULTHANDLER_REGISTER_PY_METHODDEF \
{"register", _PyCFunction_CAST(faulthandler_register_py), METH_FASTCALL|METH_KEYWORDS, faulthandler_register_py__doc__},
static PyObject *
faulthandler_register_py_impl(PyObject *module, int signum, PyObject *file,
- int all_threads, int chain);
+ int all_threads, int chain,
+ Py_ssize_t max_threads);
static PyObject *
faulthandler_register_py(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 4
+ #define NUM_KEYWORDS 5
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(signum), &_Py_ID(file), &_Py_ID(all_threads), &_Py_ID(chain), },
+ .ob_item = { &_Py_ID(signum), &_Py_ID(file), &_Py_ID(all_threads), &_Py_ID(chain), &_Py_ID(max_threads), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"signum", "file", "all_threads", "chain", NULL};
+ static const char * const _keywords[] = {"signum", "file", "all_threads", "chain", "max_threads", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "register",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[4];
+ PyObject *argsbuf[5];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
int signum;
PyObject *file = NULL;
int all_threads = 1;
int chain = 0;
+ Py_ssize_t max_threads = 100;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 1, /*maxpos*/ 4, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
goto skip_optional_pos;
}
}
- chain = PyObject_IsTrue(args[3]);
- if (chain < 0) {
- goto exit;
+ if (args[3]) {
+ chain = PyObject_IsTrue(args[3]);
+ if (chain < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
}
skip_optional_pos:
- return_value = faulthandler_register_py_impl(module, signum, file, all_threads, chain);
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[4]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ max_threads = ival;
+ }
+skip_optional_kwonly:
+ return_value = faulthandler_register_py_impl(module, signum, file, all_threads, chain, max_threads);
exit:
return return_value;
#ifndef FAULTHANDLER__RAISE_EXCEPTION_METHODDEF
#define FAULTHANDLER__RAISE_EXCEPTION_METHODDEF
#endif /* !defined(FAULTHANDLER__RAISE_EXCEPTION_METHODDEF) */
-/*[clinic end generated code: output=31bf0149d0d02ccf input=a9049054013a1b77]*/
+/*[clinic end generated code: output=2452d767c85130a6 input=a9049054013a1b77]*/
static void
faulthandler_dump_traceback(int fd, int all_threads,
- PyInterpreterState *interp)
+ PyInterpreterState *interp,
+ Py_ssize_t max_threads)
{
static volatile int reentrant = 0;
PyThreadState *tstate = PyGILState_GetThisThreadState();
if (all_threads == 1) {
- (void)_Py_DumpTracebackThreads(fd, NULL, tstate);
+ (void)_Py_DumpTracebackThreads(fd, NULL, tstate, max_threads);
}
else {
if (all_threads == FT_IGNORE_ALL_THREADS) {
file: object(py_default="sys.stderr") = NULL
all_threads: bool = True
+ *
+ max_threads: Py_ssize_t = 100
Dump the traceback of the current thread into file.
-Dump the traceback of all threads if all_threads is true.
+Dump the traceback of all threads if all_threads is true. max_threads
+caps the number of threads dumped.
[clinic start generated code]*/
static PyObject *
faulthandler_dump_traceback_py_impl(PyObject *module, PyObject *file,
- int all_threads)
-/*[clinic end generated code: output=34efece0ca18314f input=b832ec55e27a7898]*/
+ int all_threads, Py_ssize_t max_threads)
+/*[clinic end generated code: output=ee1bbc2668e56e77 input=38630eb40e641de6]*/
{
PyThreadState *tstate;
const char *errmsg;
/* 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 = _Py_DumpTracebackThreads(fd, NULL, tstate, max_threads);
_PyEval_StartTheWorld(interp);
if (errmsg != NULL) {
PyErr_SetString(PyExc_RuntimeError, errmsg);
}
faulthandler_dump_traceback(fd, deduce_all_threads(),
- fatal_error.interp);
+ fatal_error.interp,
+ fatal_error.max_threads);
faulthandler_dump_c_stack(fd);
_Py_DumpExtensionModules(fd, fatal_error.interp);
}
faulthandler_dump_traceback(fd, deduce_all_threads(),
- fatal_error.interp);
+ fatal_error.interp,
+ fatal_error.max_threads);
faulthandler_dump_c_stack(fd);
/* call the next exception handler */
file: object(py_default="sys.stderr") = NULL
all_threads: bool = True
c_stack: bool = True
+ *
+ max_threads: Py_ssize_t = 100
Enable the fault handler.
[clinic start generated code]*/
static PyObject *
faulthandler_py_enable_impl(PyObject *module, PyObject *file,
- int all_threads, int c_stack)
-/*[clinic end generated code: output=580d89b5eb62f1cb input=77277746a88b25ca]*/
+ int all_threads, int c_stack,
+ Py_ssize_t max_threads)
+/*[clinic end generated code: output=7ee655332317c47a input=e64759714f27b466]*/
{
int fd;
PyThreadState *tstate;
fatal_error.all_threads = all_threads;
fatal_error.interp = PyThreadState_GetInterpreter(tstate);
fatal_error.c_stack = c_stack;
+ fatal_error.max_threads = max_threads;
if (faulthandler_enable() < 0) {
return NULL;
(void)_Py_write_noraise(thread.fd, thread.header, (int)thread.header_len);
- errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, NULL);
+ errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, NULL,
+ thread.max_threads);
ok = (errmsg == NULL);
if (thread.exit)
repeat: bool = False
file: object(py_default="sys.stderr") = NULL
exit: bool = False
+ *
+ max_threads: Py_ssize_t = 100
Dump the traceback of all threads in timeout seconds.
If repeat is true, the tracebacks of all threads are dumped every timeout
-seconds. If exit is true, call _exit(1) which is not safe.
+seconds. If exit is true, call _exit(1) which is not safe. max_threads
+caps the number of threads dumped.
[clinic start generated code]*/
static PyObject *
faulthandler_dump_traceback_later_impl(PyObject *module,
PyObject *timeout_obj, int repeat,
- PyObject *file, int exit)
-/*[clinic end generated code: output=a24d80d694d25ba2 input=fd005625ecc2ba9a]*/
+ PyObject *file, int exit,
+ Py_ssize_t max_threads)
+/*[clinic end generated code: output=543a0f3807113394 input=6836555ee157ddb4]*/
{
PyTime_t timeout, timeout_us;
int fd;
thread.exit = exit;
thread.header = header;
thread.header_len = header_len;
+ thread.max_threads = max_threads;
/* Arm these locks to serve as events when released */
PyThread_acquire_lock(thread.running, 1);
if (!user->enabled)
return;
- faulthandler_dump_traceback(user->fd, user->all_threads, user->interp);
+ faulthandler_dump_traceback(user->fd, user->all_threads, user->interp,
+ user->max_threads);
#ifdef HAVE_SIGACTION
if (user->chain) {
file: object(py_default="sys.stderr") = NULL
all_threads: bool = True
chain: bool = False
+ *
+ max_threads: Py_ssize_t = 100
Register a handler for the signal 'signum'.
Dump the traceback of the current thread, or of all threads if
-all_threads is True, into file.
+all_threads is True, into file. max_threads caps the number of threads
+dumped.
[clinic start generated code]*/
static PyObject *
faulthandler_register_py_impl(PyObject *module, int signum, PyObject *file,
- int all_threads, int chain)
-/*[clinic end generated code: output=1f770cee150a56cd input=ae9de829e850907b]*/
+ int all_threads, int chain,
+ Py_ssize_t max_threads)
+/*[clinic end generated code: output=d63a5b4f388dee5f input=c75096a20de502fe]*/
{
int fd;
user_signal_t *user;
user->all_threads = all_threads;
user->chain = chain;
user->interp = PyThreadState_GetInterpreter(tstate);
+ user->max_threads = max_threads;
user->enabled = 1;
Py_RETURN_NONE;
/* display the current Python stack */
#ifndef Py_GIL_DISABLED
- _Py_DumpTracebackThreads(fd, interp, tstate);
+ _Py_DumpTracebackThreads(fd, interp, tstate, 0);
#else
_Py_DumpTraceback(fd, tstate);
#endif
#define MAX_STRING_LENGTH 500
#define MAX_FRAME_DEPTH 100
-#define MAX_NTHREADS 100
+#define DEFAULT_MAX_NTHREADS 100
/* Function from Parser/tokenizer/file_tokenizer.c */
extern char* _PyTokenizer_FindEncodingFilename(int, PyObject *);
handlers if signals were received. */
const char* _Py_NO_SANITIZE_THREAD
_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
- PyThreadState *current_tstate)
+ PyThreadState *current_tstate,
+ Py_ssize_t max_threads)
{
+ if (max_threads == 0) {
+ max_threads = DEFAULT_MAX_NTHREADS;
+ }
+
if (current_tstate == NULL) {
/* _Py_DumpTracebackThreads() is called from signal handlers by
faulthandler.
return "unable to get the thread head state";
/* Dump the traceback of each thread */
- unsigned int nthreads = 0;
+ Py_ssize_t nthreads = 0;
_Py_BEGIN_SUPPRESS_IPH
do
{
if (nthreads != 0)
PUTS(fd, "\n");
- if (nthreads >= MAX_NTHREADS) {
+ if (nthreads >= max_threads) {
PUTS(fd, "...\n");
break;
}