About a month ago, we noticed that the 'fatal-signal' module was not really multithread-safe — because it used sigprocmask, assuming that sigprocmask would block some signals process-wide. An assumption that just does not hold.
This patch now fixes it, thanks to the 'sigdelay' module. 2026-05-16 Bruno Haible <[email protected]> fatal-signal: Make really multithread-safe. * lib/fatal-signal.h (block_fatal_signals): Document a constraint regarding thread creation. * lib/fatal-signal.c: Include sigdelay.h. (fatal_signals_block_initially_mt): New variable. (block_fatal_signals, unblock_fatal_signals): In a multithreaded process, use sigdelay instead of pthread_sigmask. * modules/fatal-signal (Depends-on): Add sigdelay. diff --git a/lib/fatal-signal.c b/lib/fatal-signal.c index 33c99d5b8c..c8fb328bbb 100644 --- a/lib/fatal-signal.c +++ b/lib/fatal-signal.c @@ -29,6 +29,7 @@ #include "glthread/lock.h" #include "glthread/once.h" #include "thread-optim.h" +#include "sigdelay.h" #include "sig-handler.h" /* ========================================================================= */ @@ -299,6 +300,8 @@ init_fatal_signal_set (void) to occur in different threads and even overlap in time. */ gl_lock_define_initialized (static, fatal_signals_block_lock) static unsigned int fatal_signals_block_counter = 0; +/* For correct operation in the face of thread-optim.h. */ +static bool fatal_signals_block_initially_mt; /* Temporarily delay the catchable fatal signals. */ void @@ -310,8 +313,22 @@ block_fatal_signals (void) if (fatal_signals_block_counter++ == 0) { + fatal_signals_block_initially_mt = mt; init_fatal_signal_set (); - pthread_sigmask (SIG_BLOCK, &fatal_signal_set, NULL); + if (mt) + sigdelay (SIG_BLOCK, &fatal_signal_set, NULL); + else + pthread_sigmask (SIG_BLOCK, &fatal_signal_set, NULL); + } + else + { + if (!fatal_signals_block_initially_mt && mt) + { + /* The process was single-threaded and has become multithreaded + before the matching unblock_fatal_signals() call. This is + a constraint violation. */ + abort (); + } } if (mt) gl_lock_unlock (fatal_signals_block_lock); @@ -331,8 +348,18 @@ unblock_fatal_signals (void) abort (); if (--fatal_signals_block_counter == 0) { + if (!fatal_signals_block_initially_mt && mt) + { + /* The process was single-threaded and has become multithreaded + at the matching unblock_fatal_signals() call. This is a + constraint violation. */ + abort (); + } init_fatal_signal_set (); - pthread_sigmask (SIG_UNBLOCK, &fatal_signal_set, NULL); + if (fatal_signals_block_initially_mt) + sigdelay (SIG_UNBLOCK, &fatal_signal_set, NULL); + else + pthread_sigmask (SIG_UNBLOCK, &fatal_signal_set, NULL); } if (mt) gl_lock_unlock (fatal_signals_block_lock); diff --git a/lib/fatal-signal.h b/lib/fatal-signal.h index 5cc3c25152..fa21308a1d 100644 --- a/lib/fatal-signal.h +++ b/lib/fatal-signal.h @@ -77,7 +77,11 @@ extern int at_fatal_signal (_GL_ASYNC_SAFE void (*function) (int sig)); /* Temporarily delay the catchable fatal signals. The signals will be blocked (= delayed) until the next call to unblock_fatal_signals(). If the signals are already blocked, a further - call to block_fatal_signals() has no effect. */ + call to block_fatal_signals() has no effect. + The program must obey the following constraint: If at the moment of a + block_fatal_signals() call the process is single-threaded, it MUST NOT + create additional threads until the matching unblock_fatal_signals() + call. */ extern void block_fatal_signals (void); /* Stop delaying the catchable fatal signals. */ diff --git a/modules/fatal-signal b/modules/fatal-signal index 8741641bf5..1561b17433 100644 --- a/modules/fatal-signal +++ b/modules/fatal-signal @@ -15,6 +15,7 @@ sigaction lock once thread-optim +sigdelay pthread_sigmask raise stdcountof-h
