summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/faulthandler.rst4
-rw-r--r--Lib/test/test_faulthandler.py31
-rw-r--r--Modules/faulthandler.c92
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")},