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
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.
......
......@@ -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)
......
......@@ -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")},
......
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