mirror of https://github.com/python/cpython.git
Issue #12550: Add chain optional argument to faulthandler.register()
Call the previous signal handler if chain is True.
This commit is contained in:
parent
d93da2b952
commit
a9a9dab042
|
@ -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 @@ def test_dump_tracebacks_later_twice(self):
|
||||||
@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 @@ def check_register(self, filename=False, all_threads=False,
|
||||||
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 @@ def func(signum):
|
||||||
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 @@ def func(signum):
|
||||||
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 @@ def test_register_file(self):
|
||||||
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 @@
|
||||||
#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 {
|
||||||
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;
|
||||||
# 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)
|
||||||
|
|
||||||
/* 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)
|
||||||
#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)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
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,
|
||||||
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[] = {
|
||||||
|
|
||||||
#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")},
|
||||||
|
|
Loading…
Reference in New Issue