bpo-37851: faulthandler allocates its stack on demand (GH-15358)

The faulthandler module no longer allocates its alternative stack at
Python startup. Now the stack is only allocated at the first
faulthandler usage.

faulthandler no longer ignores memory allocation failure when
allocating the stack. sigaltstack() failure now raises an OSError
exception, rather than being ignored.

The alternative stack is no longer used if sigaction() is
not available. In practice, sigaltstack() should only be available
when sigaction() is avaialble, so this change should have no effect
in practice.

faulthandler.dump_traceback_later() internal locks are now only
allocated at the first dump_traceback_later() call, rather than
always being allocated at Python startup.
This commit is contained in:
Victor Stinner 2019-08-21 13:40:42 +01:00 committed by GitHub
parent e0b6117e27
commit d8c5adf6f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 101 additions and 48 deletions

View File

@ -0,0 +1,3 @@
The :mod:`faulthandler` module no longer allocates its alternative stack at
Python startup. Now the stack is only allocated at the first faulthandler
usage.

View File

@ -125,7 +125,13 @@ static fault_handler_t faulthandler_handlers[] = {
static const size_t faulthandler_nsignals = \ static const size_t faulthandler_nsignals = \
Py_ARRAY_LENGTH(faulthandler_handlers); Py_ARRAY_LENGTH(faulthandler_handlers);
#ifdef HAVE_SIGALTSTACK /* Using an alternative stack requires sigaltstack()
and sigaction() SA_ONSTACK */
#if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)
# define FAULTHANDLER_USE_ALT_STACK
#endif
#ifdef FAULTHANDLER_USE_ALT_STACK
static stack_t stack; static stack_t stack;
static stack_t old_stack; static stack_t old_stack;
#endif #endif
@ -427,6 +433,36 @@ faulthandler_exc_handler(struct _EXCEPTION_POINTERS *exc_info)
} }
#endif #endif
#ifdef FAULTHANDLER_USE_ALT_STACK
static int
faulthandler_allocate_stack(void)
{
if (stack.ss_sp != NULL) {
return 0;
}
/* Allocate an alternate stack for faulthandler() signal handler
to be able to execute a signal handler on a stack overflow error */
stack.ss_sp = PyMem_Malloc(stack.ss_size);
if (stack.ss_sp == NULL) {
PyErr_NoMemory();
return -1;
}
int err = sigaltstack(&stack, &old_stack);
if (err) {
/* Release the stack to retry sigaltstack() next time */
PyMem_Free(stack.ss_sp);
stack.ss_sp = NULL;
PyErr_SetFromErrno(PyExc_OSError);
return -1;
}
return 0;
}
#endif
/* Install the handler for fatal signals, faulthandler_fatal_error(). */ /* Install the handler for fatal signals, faulthandler_fatal_error(). */
static int static int
@ -437,32 +473,35 @@ faulthandler_enable(void)
} }
fatal_error.enabled = 1; fatal_error.enabled = 1;
#ifdef FAULTHANDLER_USE_ALT_STACK
if (faulthandler_allocate_stack() < 0) {
return -1;
}
#endif
for (size_t i=0; i < faulthandler_nsignals; i++) { for (size_t i=0; i < faulthandler_nsignals; i++) {
fault_handler_t *handler; fault_handler_t *handler;
#ifdef HAVE_SIGACTION
struct sigaction action;
#endif
int err; int err;
handler = &faulthandler_handlers[i]; handler = &faulthandler_handlers[i];
assert(!handler->enabled); assert(!handler->enabled);
#ifdef HAVE_SIGACTION #ifdef HAVE_SIGACTION
struct sigaction action;
action.sa_handler = faulthandler_fatal_error; action.sa_handler = faulthandler_fatal_error;
sigemptyset(&action.sa_mask); sigemptyset(&action.sa_mask);
/* Do not prevent the signal from being received from within /* Do not prevent the signal from being received from within
its own signal handler */ its own signal handler */
action.sa_flags = SA_NODEFER; action.sa_flags = SA_NODEFER;
#ifdef HAVE_SIGALTSTACK #ifdef FAULTHANDLER_USE_ALT_STACK
if (stack.ss_sp != NULL) { assert(stack.ss_sp != NULL);
/* Call the signal handler on an alternate signal stack /* Call the signal handler on an alternate signal stack
provided by sigaltstack() */ provided by sigaltstack() */
action.sa_flags |= SA_ONSTACK; action.sa_flags |= SA_ONSTACK;
}
#endif #endif
err = sigaction(handler->signum, &action, &handler->previous); err = sigaction(handler->signum, &action, &handler->previous);
#else #else
handler->previous = signal(handler->signum, handler->previous = signal(handler->signum,
faulthandler_fatal_error); faulthandler_fatal_error);
err = (handler->previous == SIG_ERR); err = (handler->previous == SIG_ERR);
#endif #endif
if (err) { if (err) {
@ -676,17 +715,37 @@ faulthandler_dump_traceback_later(PyObject *self,
} }
tstate = get_thread_state(); tstate = get_thread_state();
if (tstate == NULL) if (tstate == NULL) {
return NULL; return NULL;
}
fd = faulthandler_get_fileno(&file); fd = faulthandler_get_fileno(&file);
if (fd < 0) if (fd < 0) {
return NULL; return NULL;
}
if (!thread.running) {
thread.running = PyThread_allocate_lock();
if (!thread.running) {
return PyErr_NoMemory();
}
}
if (!thread.cancel_event) {
thread.cancel_event = PyThread_allocate_lock();
if (!thread.cancel_event || !thread.running) {
return PyErr_NoMemory();
}
/* cancel_event starts to be acquired: it's only released to cancel
the thread. */
PyThread_acquire_lock(thread.cancel_event, 1);
}
/* format the timeout */ /* format the timeout */
header = format_timeout(timeout_us); header = format_timeout(timeout_us);
if (header == NULL) if (header == NULL) {
return PyErr_NoMemory(); return PyErr_NoMemory();
}
header_len = strlen(header); header_len = strlen(header);
/* Cancel previous thread, if running */ /* Cancel previous thread, if running */
@ -728,9 +787,10 @@ faulthandler_cancel_dump_traceback_later_py(PyObject *self,
} }
#endif /* FAULTHANDLER_LATER */ #endif /* FAULTHANDLER_LATER */
#ifdef FAULTHANDLER_USER #ifdef FAULTHANDLER_USER
static int static int
faulthandler_register(int signum, int chain, _Py_sighandler_t *p_previous) faulthandler_register(int signum, int chain, _Py_sighandler_t *previous_p)
{ {
#ifdef HAVE_SIGACTION #ifdef HAVE_SIGACTION
struct sigaction action; struct sigaction action;
@ -745,19 +805,19 @@ faulthandler_register(int signum, int chain, _Py_sighandler_t *p_previous)
own signal handler */ own signal handler */
action.sa_flags = SA_NODEFER; action.sa_flags = SA_NODEFER;
} }
#ifdef HAVE_SIGALTSTACK #ifdef FAULTHANDLER_USE_ALT_STACK
if (stack.ss_sp != NULL) { assert(stack.ss_sp != NULL);
/* Call the signal handler on an alternate signal stack /* Call the signal handler on an alternate signal stack
provided by sigaltstack() */ provided by sigaltstack() */
action.sa_flags |= SA_ONSTACK; action.sa_flags |= SA_ONSTACK;
}
#endif #endif
return sigaction(signum, &action, p_previous); return sigaction(signum, &action, previous_p);
#else #else
_Py_sighandler_t previous; _Py_sighandler_t previous;
previous = signal(signum, faulthandler_user); previous = signal(signum, faulthandler_user);
if (p_previous != NULL) if (previous_p != NULL) {
*p_previous = previous; *previous_p = previous;
}
return (previous == SIG_ERR); return (previous == SIG_ERR);
#endif #endif
} }
@ -861,6 +921,12 @@ faulthandler_register_py(PyObject *self,
user = &user_signals[signum]; user = &user_signals[signum];
if (!user->enabled) { if (!user->enabled) {
#ifdef FAULTHANDLER_USE_ALT_STACK
if (faulthandler_allocate_stack() < 0) {
return NULL;
}
#endif
err = faulthandler_register(signum, chain, &previous); err = faulthandler_register(signum, chain, &previous);
if (err) { if (err) {
PyErr_SetFromErrno(PyExc_OSError); PyErr_SetFromErrno(PyExc_OSError);
@ -1094,7 +1160,7 @@ faulthandler_fatal_error_py(PyObject *self, PyObject *args)
Py_RETURN_NONE; Py_RETURN_NONE;
} }
#if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION) #if defined(FAULTHANDLER_USE_ALT_STACK)
#define FAULTHANDLER_STACK_OVERFLOW #define FAULTHANDLER_STACK_OVERFLOW
#ifdef __INTEL_COMPILER #ifdef __INTEL_COMPILER
@ -1153,7 +1219,7 @@ faulthandler_stack_overflow(PyObject *self, PyObject *Py_UNUSED(ignored))
size, depth); size, depth);
return NULL; return NULL;
} }
#endif /* defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION) */ #endif /* defined(FAULTHANDLER_USE_ALT_STACK) && defined(HAVE_SIGACTION) */
static int static int
@ -1318,35 +1384,18 @@ faulthandler_init_enable(void)
PyStatus PyStatus
_PyFaulthandler_Init(int enable) _PyFaulthandler_Init(int enable)
{ {
#ifdef HAVE_SIGALTSTACK #ifdef FAULTHANDLER_USE_ALT_STACK
int err; memset(&stack, 0, sizeof(stack));
/* Try to allocate an alternate stack for faulthandler() signal handler to
* be able to allocate memory on the stack, even on a stack overflow. If it
* fails, ignore the error. */
stack.ss_flags = 0; stack.ss_flags = 0;
/* bpo-21131: allocate dedicated stack of SIGSTKSZ*2 bytes, instead of just /* bpo-21131: allocate dedicated stack of SIGSTKSZ*2 bytes, instead of just
SIGSTKSZ bytes. Calling the previous signal handler in faulthandler SIGSTKSZ bytes. Calling the previous signal handler in faulthandler
signal handler uses more than SIGSTKSZ bytes of stack memory on some signal handler uses more than SIGSTKSZ bytes of stack memory on some
platforms. */ platforms. */
stack.ss_size = SIGSTKSZ * 2; stack.ss_size = SIGSTKSZ * 2;
stack.ss_sp = PyMem_Malloc(stack.ss_size);
if (stack.ss_sp != NULL) {
err = sigaltstack(&stack, &old_stack);
if (err) {
PyMem_Free(stack.ss_sp);
stack.ss_sp = NULL;
}
}
#endif #endif
#ifdef FAULTHANDLER_LATER #ifdef FAULTHANDLER_LATER
thread.file = NULL; memset(&thread, 0, sizeof(thread));
thread.cancel_event = PyThread_allocate_lock();
thread.running = PyThread_allocate_lock();
if (!thread.cancel_event || !thread.running) {
return _PyStatus_ERR("failed to allocate locks for faulthandler");
}
PyThread_acquire_lock(thread.cancel_event, 1);
#endif #endif
if (enable) { if (enable) {
@ -1386,7 +1435,8 @@ void _PyFaulthandler_Fini(void)
/* fatal */ /* fatal */
faulthandler_disable(); faulthandler_disable();
#ifdef HAVE_SIGALTSTACK
#ifdef FAULTHANDLER_USE_ALT_STACK
if (stack.ss_sp != NULL) { if (stack.ss_sp != NULL) {
/* Fetch the current alt stack */ /* Fetch the current alt stack */
stack_t current_stack; stack_t current_stack;