Skip to content

Commit d8c5adf

Browse files
authored
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.
1 parent e0b6117 commit d8c5adf

File tree

2 files changed

+101
-48
lines changed

2 files changed

+101
-48
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The :mod:`faulthandler` module no longer allocates its alternative stack at
2+
Python startup. Now the stack is only allocated at the first faulthandler
3+
usage.

Modules/faulthandler.c

+98-48
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,13 @@ static fault_handler_t faulthandler_handlers[] = {
125125
static const size_t faulthandler_nsignals = \
126126
Py_ARRAY_LENGTH(faulthandler_handlers);
127127

128-
#ifdef HAVE_SIGALTSTACK
128+
/* Using an alternative stack requires sigaltstack()
129+
and sigaction() SA_ONSTACK */
130+
#if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)
131+
# define FAULTHANDLER_USE_ALT_STACK
132+
#endif
133+
134+
#ifdef FAULTHANDLER_USE_ALT_STACK
129135
static stack_t stack;
130136
static stack_t old_stack;
131137
#endif
@@ -427,6 +433,36 @@ faulthandler_exc_handler(struct _EXCEPTION_POINTERS *exc_info)
427433
}
428434
#endif
429435

436+
437+
#ifdef FAULTHANDLER_USE_ALT_STACK
438+
static int
439+
faulthandler_allocate_stack(void)
440+
{
441+
if (stack.ss_sp != NULL) {
442+
return 0;
443+
}
444+
/* Allocate an alternate stack for faulthandler() signal handler
445+
to be able to execute a signal handler on a stack overflow error */
446+
stack.ss_sp = PyMem_Malloc(stack.ss_size);
447+
if (stack.ss_sp == NULL) {
448+
PyErr_NoMemory();
449+
return -1;
450+
}
451+
452+
int err = sigaltstack(&stack, &old_stack);
453+
if (err) {
454+
/* Release the stack to retry sigaltstack() next time */
455+
PyMem_Free(stack.ss_sp);
456+
stack.ss_sp = NULL;
457+
458+
PyErr_SetFromErrno(PyExc_OSError);
459+
return -1;
460+
}
461+
return 0;
462+
}
463+
#endif
464+
465+
430466
/* Install the handler for fatal signals, faulthandler_fatal_error(). */
431467

432468
static int
@@ -437,32 +473,35 @@ faulthandler_enable(void)
437473
}
438474
fatal_error.enabled = 1;
439475

476+
#ifdef FAULTHANDLER_USE_ALT_STACK
477+
if (faulthandler_allocate_stack() < 0) {
478+
return -1;
479+
}
480+
#endif
481+
440482
for (size_t i=0; i < faulthandler_nsignals; i++) {
441483
fault_handler_t *handler;
442-
#ifdef HAVE_SIGACTION
443-
struct sigaction action;
444-
#endif
445484
int err;
446485

447486
handler = &faulthandler_handlers[i];
448487
assert(!handler->enabled);
449488
#ifdef HAVE_SIGACTION
489+
struct sigaction action;
450490
action.sa_handler = faulthandler_fatal_error;
451491
sigemptyset(&action.sa_mask);
452492
/* Do not prevent the signal from being received from within
453493
its own signal handler */
454494
action.sa_flags = SA_NODEFER;
455-
#ifdef HAVE_SIGALTSTACK
456-
if (stack.ss_sp != NULL) {
457-
/* Call the signal handler on an alternate signal stack
458-
provided by sigaltstack() */
459-
action.sa_flags |= SA_ONSTACK;
460-
}
495+
#ifdef FAULTHANDLER_USE_ALT_STACK
496+
assert(stack.ss_sp != NULL);
497+
/* Call the signal handler on an alternate signal stack
498+
provided by sigaltstack() */
499+
action.sa_flags |= SA_ONSTACK;
461500
#endif
462501
err = sigaction(handler->signum, &action, &handler->previous);
463502
#else
464503
handler->previous = signal(handler->signum,
465-
faulthandler_fatal_error);
504+
faulthandler_fatal_error);
466505
err = (handler->previous == SIG_ERR);
467506
#endif
468507
if (err) {
@@ -676,17 +715,37 @@ faulthandler_dump_traceback_later(PyObject *self,
676715
}
677716

678717
tstate = get_thread_state();
679-
if (tstate == NULL)
718+
if (tstate == NULL) {
680719
return NULL;
720+
}
681721

682722
fd = faulthandler_get_fileno(&file);
683-
if (fd < 0)
723+
if (fd < 0) {
684724
return NULL;
725+
}
726+
727+
if (!thread.running) {
728+
thread.running = PyThread_allocate_lock();
729+
if (!thread.running) {
730+
return PyErr_NoMemory();
731+
}
732+
}
733+
if (!thread.cancel_event) {
734+
thread.cancel_event = PyThread_allocate_lock();
735+
if (!thread.cancel_event || !thread.running) {
736+
return PyErr_NoMemory();
737+
}
738+
739+
/* cancel_event starts to be acquired: it's only released to cancel
740+
the thread. */
741+
PyThread_acquire_lock(thread.cancel_event, 1);
742+
}
685743

686744
/* format the timeout */
687745
header = format_timeout(timeout_us);
688-
if (header == NULL)
746+
if (header == NULL) {
689747
return PyErr_NoMemory();
748+
}
690749
header_len = strlen(header);
691750

692751
/* Cancel previous thread, if running */
@@ -728,9 +787,10 @@ faulthandler_cancel_dump_traceback_later_py(PyObject *self,
728787
}
729788
#endif /* FAULTHANDLER_LATER */
730789

790+
731791
#ifdef FAULTHANDLER_USER
732792
static int
733-
faulthandler_register(int signum, int chain, _Py_sighandler_t *p_previous)
793+
faulthandler_register(int signum, int chain, _Py_sighandler_t *previous_p)
734794
{
735795
#ifdef HAVE_SIGACTION
736796
struct sigaction action;
@@ -745,19 +805,19 @@ faulthandler_register(int signum, int chain, _Py_sighandler_t *p_previous)
745805
own signal handler */
746806
action.sa_flags = SA_NODEFER;
747807
}
748-
#ifdef HAVE_SIGALTSTACK
749-
if (stack.ss_sp != NULL) {
750-
/* Call the signal handler on an alternate signal stack
751-
provided by sigaltstack() */
752-
action.sa_flags |= SA_ONSTACK;
753-
}
808+
#ifdef FAULTHANDLER_USE_ALT_STACK
809+
assert(stack.ss_sp != NULL);
810+
/* Call the signal handler on an alternate signal stack
811+
provided by sigaltstack() */
812+
action.sa_flags |= SA_ONSTACK;
754813
#endif
755-
return sigaction(signum, &action, p_previous);
814+
return sigaction(signum, &action, previous_p);
756815
#else
757816
_Py_sighandler_t previous;
758817
previous = signal(signum, faulthandler_user);
759-
if (p_previous != NULL)
760-
*p_previous = previous;
818+
if (previous_p != NULL) {
819+
*previous_p = previous;
820+
}
761821
return (previous == SIG_ERR);
762822
#endif
763823
}
@@ -861,6 +921,12 @@ faulthandler_register_py(PyObject *self,
861921
user = &user_signals[signum];
862922

863923
if (!user->enabled) {
924+
#ifdef FAULTHANDLER_USE_ALT_STACK
925+
if (faulthandler_allocate_stack() < 0) {
926+
return NULL;
927+
}
928+
#endif
929+
864930
err = faulthandler_register(signum, chain, &previous);
865931
if (err) {
866932
PyErr_SetFromErrno(PyExc_OSError);
@@ -1094,7 +1160,7 @@ faulthandler_fatal_error_py(PyObject *self, PyObject *args)
10941160
Py_RETURN_NONE;
10951161
}
10961162

1097-
#if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)
1163+
#if defined(FAULTHANDLER_USE_ALT_STACK)
10981164
#define FAULTHANDLER_STACK_OVERFLOW
10991165

11001166
#ifdef __INTEL_COMPILER
@@ -1153,7 +1219,7 @@ faulthandler_stack_overflow(PyObject *self, PyObject *Py_UNUSED(ignored))
11531219
size, depth);
11541220
return NULL;
11551221
}
1156-
#endif /* defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION) */
1222+
#endif /* defined(FAULTHANDLER_USE_ALT_STACK) && defined(HAVE_SIGACTION) */
11571223

11581224

11591225
static int
@@ -1318,35 +1384,18 @@ faulthandler_init_enable(void)
13181384
PyStatus
13191385
_PyFaulthandler_Init(int enable)
13201386
{
1321-
#ifdef HAVE_SIGALTSTACK
1322-
int err;
1323-
1324-
/* Try to allocate an alternate stack for faulthandler() signal handler to
1325-
* be able to allocate memory on the stack, even on a stack overflow. If it
1326-
* fails, ignore the error. */
1387+
#ifdef FAULTHANDLER_USE_ALT_STACK
1388+
memset(&stack, 0, sizeof(stack));
13271389
stack.ss_flags = 0;
13281390
/* bpo-21131: allocate dedicated stack of SIGSTKSZ*2 bytes, instead of just
13291391
SIGSTKSZ bytes. Calling the previous signal handler in faulthandler
13301392
signal handler uses more than SIGSTKSZ bytes of stack memory on some
13311393
platforms. */
13321394
stack.ss_size = SIGSTKSZ * 2;
1333-
stack.ss_sp = PyMem_Malloc(stack.ss_size);
1334-
if (stack.ss_sp != NULL) {
1335-
err = sigaltstack(&stack, &old_stack);
1336-
if (err) {
1337-
PyMem_Free(stack.ss_sp);
1338-
stack.ss_sp = NULL;
1339-
}
1340-
}
13411395
#endif
1396+
13421397
#ifdef FAULTHANDLER_LATER
1343-
thread.file = NULL;
1344-
thread.cancel_event = PyThread_allocate_lock();
1345-
thread.running = PyThread_allocate_lock();
1346-
if (!thread.cancel_event || !thread.running) {
1347-
return _PyStatus_ERR("failed to allocate locks for faulthandler");
1348-
}
1349-
PyThread_acquire_lock(thread.cancel_event, 1);
1398+
memset(&thread, 0, sizeof(thread));
13501399
#endif
13511400

13521401
if (enable) {
@@ -1386,7 +1435,8 @@ void _PyFaulthandler_Fini(void)
13861435

13871436
/* fatal */
13881437
faulthandler_disable();
1389-
#ifdef HAVE_SIGALTSTACK
1438+
1439+
#ifdef FAULTHANDLER_USE_ALT_STACK
13901440
if (stack.ss_sp != NULL) {
13911441
/* Fetch the current alt stack */
13921442
stack_t current_stack;

0 commit comments

Comments
 (0)