diff options
-rw-r--r-- | Doc/library/faulthandler.rst | 4 | ||||
-rw-r--r-- | Lib/test/test_faulthandler.py | 31 | ||||
-rw-r--r-- | Modules/faulthandler.c | 92 |
3 files changed, 90 insertions, 37 deletions
diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst index 3e75c60..c9b9546 100644 --- a/Doc/library/faulthandler.rst +++ b/Doc/library/faulthandler.rst @@ -92,11 +92,11 @@ Dump the tracebacks after a timeout Dump the traceback on a user signal ----------------------------------- -.. function:: register(signum, file=sys.stderr, all_threads=True) +.. function:: register(signum, file=sys.stderr, all_threads=True, chain=False) 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*. + ``False``, into *file*. Call the previous handler if chain is ``True``. Not available on Windows. diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index 2a254af..c8a8030 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -452,11 +452,13 @@ if file is not None: @unittest.skipIf(not hasattr(faulthandler, "register"), "need faulthandler.register") def check_register(self, filename=False, all_threads=False, - unregister=False): + unregister=False, chain=False): """ Register a handler displaying the traceback on a user signal. Raise the signal and check the written traceback. + If chain is True, check that the previous signal handler is called. + Raise an error if the output doesn't match the expected format. """ signum = signal.SIGUSR1 @@ -464,22 +466,41 @@ if file is not None: import faulthandler import os import signal +import sys def func(signum): os.kill(os.getpid(), signum) +def handler(signum, frame): + handler.called = True +handler.called = False + +exitcode = 0 signum = {signum} unregister = {unregister} +chain = {chain} + if {has_filename}: file = open({filename}, "wb") else: file = None -faulthandler.register(signum, file=file, all_threads={all_threads}) +if chain: + signal.signal(signum, handler) +faulthandler.register(signum, file=file, + all_threads={all_threads}, chain={chain}) if unregister: faulthandler.unregister(signum) func(signum) +if chain and not handler.called: + if file is not None: + output = file + else: + output = sys.stderr + print("Error: signal handler not called!", file=output) + exitcode = 1 if file is not None: file.close() +sys.exit(exitcode) """.strip() code = code.format( filename=repr(filename), @@ -487,6 +508,7 @@ if file is not None: all_threads=all_threads, signum=signum, unregister=unregister, + chain=chain, ) trace, exitcode = self.get_output(code, filename) trace = '\n'.join(trace) @@ -495,7 +517,7 @@ if file is not None: regex = 'Current thread XXX:\n' else: regex = 'Traceback \(most recent call first\):\n' - regex = expected_traceback(6, 17, regex) + regex = expected_traceback(7, 28, regex) self.assertRegex(trace, regex) else: self.assertEqual(trace, '') @@ -517,6 +539,9 @@ if file is not None: def test_register_threads(self): self.check_register(all_threads=True) + def test_register_chain(self): + self.check_register(chain=True) + def test_main(): support.run_unittest(FaultHandlerTests) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 1450d94..b2ac83f 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -8,7 +8,6 @@ #include <pthread.h> #endif - /* Allocate at maximum 100 MB of the stack to raise the stack overflow */ #define STACK_OVERFLOW_MAX_SIZE (100*1024*1024) @@ -72,6 +71,7 @@ typedef struct { PyObject *file; int fd; int all_threads; + int chain; _Py_sighandler_t previous; PyInterpreterState *interp; } user_signal_t; @@ -94,6 +94,7 @@ static user_signal_t *user_signals; # endif #endif +static void faulthandler_user(int signum); #endif /* FAULTHANDLER_USER */ @@ -259,9 +260,9 @@ faulthandler_fatal_error(int signum) /* restore the previous handler */ #ifdef HAVE_SIGACTION - (void)sigaction(handler->signum, &handler->previous, NULL); + (void)sigaction(signum, &handler->previous, NULL); #else - (void)signal(handler->signum, handler->previous); + (void)signal(signum, handler->previous); #endif handler->enabled = 0; @@ -587,6 +588,39 @@ faulthandler_cancel_dump_tracebacks_later_py(PyObject *self) #endif /* FAULTHANDLER_LATER */ #ifdef FAULTHANDLER_USER +static int +faulthandler_register(int signum, int chain, _Py_sighandler_t *p_previous) +{ +#ifdef HAVE_SIGACTION + struct sigaction action; + action.sa_handler = faulthandler_user; + sigemptyset(&action.sa_mask); + /* if the signal is received while the kernel is executing a system + call, try to restart the system call instead of interrupting it and + return EINTR. */ + action.sa_flags = SA_RESTART; + if (chain) { + /* do not prevent the signal from being received from within its + own signal handler */ + action.sa_flags = SA_NODEFER; + } +#ifdef HAVE_SIGALTSTACK + if (stack.ss_sp != NULL) { + /* Call the signal handler on an alternate signal stack + provided by sigaltstack() */ + action.sa_flags |= SA_ONSTACK; + } +#endif + return sigaction(signum, &action, p_previous); +#else + _Py_sighandler_t previous; + previous = signal(signum, faulthandler_user); + if (p_previous != NULL) + *p_previous = previous; + return (previous == SIG_ERR); +#endif +} + /* Handler of user signals (e.g. SIGUSR1). Dump the traceback of the current thread, or of all threads if @@ -621,6 +655,19 @@ faulthandler_user(int signum) return; _Py_DumpTraceback(user->fd, tstate); } +#ifdef HAVE_SIGACTION + if (user->chain) { + (void)sigaction(signum, &user->previous, NULL); + /* call the previous signal handler */ + raise(signum); + (void)faulthandler_register(signum, user->chain, NULL); + } +#else + if (user->chain) { + /* call the previous signal handler */ + user->previous(signum); + } +#endif errno = save_errno; } @@ -646,25 +693,23 @@ check_signum(int signum) } static PyObject* -faulthandler_register(PyObject *self, - PyObject *args, PyObject *kwargs) +faulthandler_register_py(PyObject *self, + PyObject *args, PyObject *kwargs) { - static char *kwlist[] = {"signum", "file", "all_threads", NULL}; + static char *kwlist[] = {"signum", "file", "all_threads", "chain", NULL}; int signum; PyObject *file = NULL; int all_threads = 1; + int chain = 0; int fd; user_signal_t *user; _Py_sighandler_t previous; -#ifdef HAVE_SIGACTION - struct sigaction action; -#endif PyThreadState *tstate; int err; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "i|Oi:register", kwlist, - &signum, &file, &all_threads)) + "i|Oii:register", kwlist, + &signum, &file, &all_threads, &chain)) return NULL; if (!check_signum(signum)) @@ -686,25 +731,7 @@ faulthandler_register(PyObject *self, user = &user_signals[signum]; if (!user->enabled) { -#ifdef HAVE_SIGACTION - action.sa_handler = faulthandler_user; - sigemptyset(&action.sa_mask); - /* if the signal is received while the kernel is executing a system - call, try to restart the system call instead of interrupting it and - return EINTR */ - action.sa_flags = SA_RESTART; -#ifdef HAVE_SIGALTSTACK - if (stack.ss_sp != NULL) { - /* Call the signal handler on an alternate signal stack - provided by sigaltstack() */ - action.sa_flags |= SA_ONSTACK; - } -#endif - err = sigaction(signum, &action, &previous); -#else - previous = signal(signum, faulthandler_user); - err = (previous == SIG_ERR); -#endif + err = faulthandler_register(signum, chain, &previous); if (err) { PyErr_SetFromErrno(PyExc_OSError); return NULL; @@ -716,6 +743,7 @@ faulthandler_register(PyObject *self, user->file = file; user->fd = fd; user->all_threads = all_threads; + user->chain = chain; user->previous = previous; user->interp = tstate->interp; user->enabled = 1; @@ -947,8 +975,8 @@ static PyMethodDef module_methods[] = { #ifdef FAULTHANDLER_USER {"register", - (PyCFunction)faulthandler_register, METH_VARARGS|METH_KEYWORDS, - PyDoc_STR("register(signum, file=sys.stderr, all_threads=True): " + (PyCFunction)faulthandler_register_py, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("register(signum, file=sys.stderr, all_threads=True, chain=False): " "register an handler for the signal 'signum': dump the " "traceback of the current thread, or of all threads if " "all_threads is True, into file")}, |