Kaydet (Commit) a9a9dab0 authored tarafından Victor Stinner's avatar Victor Stinner

Issue #12550: Add chain optional argument to faulthandler.register()

Call the previous signal handler if chain is True.
üst d93da2b9
...@@ -92,11 +92,11 @@ Dump the tracebacks after a timeout ...@@ -92,11 +92,11 @@ Dump the tracebacks after a timeout
Dump the traceback on a user signal 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 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 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. Not available on Windows.
......
...@@ -452,11 +452,13 @@ if file is not None: ...@@ -452,11 +452,13 @@ if file is not None:
@unittest.skipIf(not hasattr(faulthandler, "register"), @unittest.skipIf(not hasattr(faulthandler, "register"),
"need faulthandler.register") "need faulthandler.register")
def check_register(self, filename=False, all_threads=False, 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 Register a handler displaying the traceback on a user signal. Raise the
signal and check the written traceback. 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. Raise an error if the output doesn't match the expected format.
""" """
signum = signal.SIGUSR1 signum = signal.SIGUSR1
...@@ -464,22 +466,41 @@ if file is not None: ...@@ -464,22 +466,41 @@ if file is not None:
import faulthandler import faulthandler
import os import os
import signal import signal
import sys
def func(signum): def func(signum):
os.kill(os.getpid(), signum) os.kill(os.getpid(), signum)
def handler(signum, frame):
handler.called = True
handler.called = False
exitcode = 0
signum = {signum} signum = {signum}
unregister = {unregister} unregister = {unregister}
chain = {chain}
if {has_filename}: if {has_filename}:
file = open({filename}, "wb") file = open({filename}, "wb")
else: else:
file = None 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: if unregister:
faulthandler.unregister(signum) faulthandler.unregister(signum)
func(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: if file is not None:
file.close() file.close()
sys.exit(exitcode)
""".strip() """.strip()
code = code.format( code = code.format(
filename=repr(filename), filename=repr(filename),
...@@ -487,6 +508,7 @@ if file is not None: ...@@ -487,6 +508,7 @@ if file is not None:
all_threads=all_threads, all_threads=all_threads,
signum=signum, signum=signum,
unregister=unregister, unregister=unregister,
chain=chain,
) )
trace, exitcode = self.get_output(code, filename) trace, exitcode = self.get_output(code, filename)
trace = '\n'.join(trace) trace = '\n'.join(trace)
...@@ -495,7 +517,7 @@ if file is not None: ...@@ -495,7 +517,7 @@ if file is not None:
regex = 'Current thread XXX:\n' regex = 'Current thread XXX:\n'
else: else:
regex = 'Traceback \(most recent call first\):\n' regex = 'Traceback \(most recent call first\):\n'
regex = expected_traceback(6, 17, regex) regex = expected_traceback(7, 28, regex)
self.assertRegex(trace, regex) self.assertRegex(trace, regex)
else: else:
self.assertEqual(trace, '') self.assertEqual(trace, '')
...@@ -517,6 +539,9 @@ if file is not None: ...@@ -517,6 +539,9 @@ if file is not None:
def test_register_threads(self): def test_register_threads(self):
self.check_register(all_threads=True) self.check_register(all_threads=True)
def test_register_chain(self):
self.check_register(chain=True)
def test_main(): def test_main():
support.run_unittest(FaultHandlerTests) support.run_unittest(FaultHandlerTests)
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
#include <pthread.h> #include <pthread.h>
#endif #endif
/* Allocate at maximum 100 MB of the stack to raise the stack overflow */ /* Allocate at maximum 100 MB of the stack to raise the stack overflow */
#define STACK_OVERFLOW_MAX_SIZE (100*1024*1024) #define STACK_OVERFLOW_MAX_SIZE (100*1024*1024)
...@@ -72,6 +71,7 @@ typedef struct { ...@@ -72,6 +71,7 @@ typedef struct {
PyObject *file; PyObject *file;
int fd; int fd;
int all_threads; int all_threads;
int chain;
_Py_sighandler_t previous; _Py_sighandler_t previous;
PyInterpreterState *interp; PyInterpreterState *interp;
} user_signal_t; } user_signal_t;
...@@ -94,6 +94,7 @@ static user_signal_t *user_signals; ...@@ -94,6 +94,7 @@ static user_signal_t *user_signals;
# endif # endif
#endif #endif
static void faulthandler_user(int signum);
#endif /* FAULTHANDLER_USER */ #endif /* FAULTHANDLER_USER */
...@@ -259,9 +260,9 @@ faulthandler_fatal_error(int signum) ...@@ -259,9 +260,9 @@ faulthandler_fatal_error(int signum)
/* restore the previous handler */ /* restore the previous handler */
#ifdef HAVE_SIGACTION #ifdef HAVE_SIGACTION
(void)sigaction(handler->signum, &handler->previous, NULL); (void)sigaction(signum, &handler->previous, NULL);
#else #else
(void)signal(handler->signum, handler->previous); (void)signal(signum, handler->previous);
#endif #endif
handler->enabled = 0; handler->enabled = 0;
...@@ -587,6 +588,39 @@ faulthandler_cancel_dump_tracebacks_later_py(PyObject *self) ...@@ -587,6 +588,39 @@ faulthandler_cancel_dump_tracebacks_later_py(PyObject *self)
#endif /* FAULTHANDLER_LATER */ #endif /* FAULTHANDLER_LATER */
#ifdef FAULTHANDLER_USER #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). /* Handler of user signals (e.g. SIGUSR1).
Dump the traceback of the current thread, or of all threads if Dump the traceback of the current thread, or of all threads if
...@@ -621,6 +655,19 @@ faulthandler_user(int signum) ...@@ -621,6 +655,19 @@ faulthandler_user(int signum)
return; return;
_Py_DumpTraceback(user->fd, tstate); _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; errno = save_errno;
} }
...@@ -646,25 +693,23 @@ check_signum(int signum) ...@@ -646,25 +693,23 @@ check_signum(int signum)
} }
static PyObject* static PyObject*
faulthandler_register(PyObject *self, faulthandler_register_py(PyObject *self,
PyObject *args, PyObject *kwargs) PyObject *args, PyObject *kwargs)
{ {
static char *kwlist[] = {"signum", "file", "all_threads", NULL}; static char *kwlist[] = {"signum", "file", "all_threads", "chain", NULL};
int signum; int signum;
PyObject *file = NULL; PyObject *file = NULL;
int all_threads = 1; int all_threads = 1;
int chain = 0;
int fd; int fd;
user_signal_t *user; user_signal_t *user;
_Py_sighandler_t previous; _Py_sighandler_t previous;
#ifdef HAVE_SIGACTION
struct sigaction action;
#endif
PyThreadState *tstate; PyThreadState *tstate;
int err; int err;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"i|Oi:register", kwlist, "i|Oii:register", kwlist,
&signum, &file, &all_threads)) &signum, &file, &all_threads, &chain))
return NULL; return NULL;
if (!check_signum(signum)) if (!check_signum(signum))
...@@ -686,25 +731,7 @@ faulthandler_register(PyObject *self, ...@@ -686,25 +731,7 @@ faulthandler_register(PyObject *self,
user = &user_signals[signum]; user = &user_signals[signum];
if (!user->enabled) { if (!user->enabled) {
#ifdef HAVE_SIGACTION err = faulthandler_register(signum, chain, &previous);
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
if (err) { if (err) {
PyErr_SetFromErrno(PyExc_OSError); PyErr_SetFromErrno(PyExc_OSError);
return NULL; return NULL;
...@@ -716,6 +743,7 @@ faulthandler_register(PyObject *self, ...@@ -716,6 +743,7 @@ faulthandler_register(PyObject *self,
user->file = file; user->file = file;
user->fd = fd; user->fd = fd;
user->all_threads = all_threads; user->all_threads = all_threads;
user->chain = chain;
user->previous = previous; user->previous = previous;
user->interp = tstate->interp; user->interp = tstate->interp;
user->enabled = 1; user->enabled = 1;
...@@ -947,8 +975,8 @@ static PyMethodDef module_methods[] = { ...@@ -947,8 +975,8 @@ static PyMethodDef module_methods[] = {
#ifdef FAULTHANDLER_USER #ifdef FAULTHANDLER_USER
{"register", {"register",
(PyCFunction)faulthandler_register, METH_VARARGS|METH_KEYWORDS, (PyCFunction)faulthandler_register_py, METH_VARARGS|METH_KEYWORDS,
PyDoc_STR("register(signum, file=sys.stderr, all_threads=True): " PyDoc_STR("register(signum, file=sys.stderr, all_threads=True, chain=False): "
"register an handler for the signal 'signum': dump the " "register an handler for the signal 'signum': dump the "
"traceback of the current thread, or of all threads if " "traceback of the current thread, or of all threads if "
"all_threads is True, into file")}, "all_threads is True, into file")},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment