2026-06-26
Niall Douglas (rationale, history)
Niall Douglas, Jens Gustedt (wording)
| number | Title | Authors | Remarks |
|---|---|---|---|
| N2471 | Stackable, thread local, signal guards | Douglas, Niall | revision 0, 2020-02-02 |
| N3540 | Modern signals handling | Douglas, Niall | revision 1, 2025-05-02 |
| N3765 | Thread-safe signals handling | Douglas, Niall | revision 2, 2025-12-14 |
| N3872 | Thread-safe signals handling | Douglas, Niall | revision 3, 2026-04-12 |
| N3783 | Working Draft | base for diff, 2025-01-15 | |
| N3924 | <this paper> | Wording Group |
Changes since N3872:
threadsafe_signals_*
to sig*.
There is a sigInstall() in
use in open source C++ code, but no siginstall() that
I could find.thrd_signal_invoke to
sigguarded.thrd_signal_raise to
stdc_raise.thrd_signal_* to
sig_*.thrd_raised_signal_info*
to stdc_siginfo*.fill_synchronous_sigset
et al to sigfillset_synchronous.siginvoke and stdc_raise functions from 7.30 which was
the wrong section to 7.14 which is the correct section.sigfence() to
remove the dependency on <stdatomic.h>.none
WG21, Austin Group
| meeting | date | for | against | abstain |
|---|---|---|---|---|
| N3872 revision 3 | ? | ? | ? | ? |
| N3765 revision 2 (March 2026 virtual N3856) | 2026-03-09 | 15 | 2 | 4 |
| N3540 revision 1 (Final Fall 2025 N3815) | 2025-08-29 | 19 | 2 | 4 |
| N2471 revision 0 (Draft April 2020 N2519) | 2020-03-30 | 5 | 2 | 6 |
Deletions in the shown standard text are as shown here,
additions, as shown here. These may render differently
according to the style in which the document is shown by your browser,
but should always be well distinguishable. In the provided style there
are two visual distinctions:
Close to each other proposed changes resemble like this.
When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects that are
neither lock-free atomic objects nor of typevolatile sig_atomic_t
not one of these categories:
- A lock-free atomic object.
- Of type
volatile sig_atomic_t.- Modified since a
sigfence().- The
void *returned bytss_async_signal_safe_get().
are unspecified, as is the state of the dynamic floating-point environment. The representation of any object modified by the handler that is
neither a lock-free atomic object nor of typevolatile sig_atomic_t
not one of these categories:
- A lock-free atomic object.
- Of type
volatile sig_atomic_t.- Accessed before a
sigfence().
becomes indeterminate when the handler exits, as does the state of the dynamic floating-point environment if it is modified by the handler and not restored to its original state.
The header
<signal.h>declaresa type and two functions and defines several macrostypes, functions and macros, for handling various signals (conditions that may be reported during program execution).
A signal can be received by a thread within the program. When a signal is received, execution is interrupted and the currently installed signal handler for that signal is executed.
There are the following categories of signal:
Synchronous: these are caused by a thread doing something. Standard signals are:
- abnormal termination
- erroneous arithmetic operation
- detection of an invalid function image
- invalid access to storage.
The implementation may define additional synchronous category signals.
Asynchronous nondebug: these are generated by the environment and are not usually generated by testing. Standard signals are:
- receipt of an interactive attention signal
- termination request sent to the program.
The implementation may define additional asynchronous nondebug category signals.
Asynchronous debug: these are generated by the environment and are usually generated by testing.
The implementation may define asynchronous debug category signals.
Async-signal-safe functions and macros are those safe to call during the handling of a signal. Only the functions of clause 7 listed below are required to be async-signal-safe, all other functions can be not async-signal-safe. The following functions are required to be async-signal-safe:
- the functions in
<stdatomic.h>(except where explicitly stated otherwise) when the atomic arguments are lock-free,- the
atomic_is_lock_freefunction with any atomic argument,- the
signalfunction with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler. Furthermore, if such a call to the signal function results in aSIG_ERRreturn, the object designated byerrnohas an indeterminate representation, or- any function within this standard explicitly described as async-signal-safe.
There are two ways to change the currently installed signal handler:
The
signalfunction globally installs a single signal handler for the whole program execution, overwriting any previously set handler.The
siginstallfunction enables an alternative signal handling mechanism which implements thread-safe composable signal handling1.
If
siginstallhas not been called in the current program execution, then the following sequence occurs on signal receipt:
- If there is such a handler, the last most recently installed handler by
signalfor that signal number is called, unless that handler was set toSIG_IGN, in which case the signal is ignored. The thread in which the handler is called is unspecified.- Otherwise, if no call of
signalfor the signal number was performed, the handler hasSIG_DFLsemantics, which is the default action for that signal number on that implementation.If
siginstallhas been called in the current program execution, then the following sequence occurs on signal receipt:
For synchronous category signals, the handler shall be called within the thread which caused the signal. For asynchronous category signals, the thread in which the handler is called is unspecified.
For each thread in a program, an ordered sequence of signal deciders is sequentially invoked to decide how to handle the signal. The ordered sequence begins with thread-locally installed signal deciders with a signal set matching the signal number in order of most recently installed last for that thread, followed by signal deciders installed globally with a signal set matching the signal number:
For thread-locally installed signal handlers, each decider function is called with a pointer to a populated
stdc_siginfo, with itsvaluemember set to the value that was specified when that decider was installed. If a decider function returns:
sig_decision_resume_execution: execution of the interrupted thread is resumed.sig_decision_call_recovery: the environment is restored to what it was when that thread-local decider was installed as ifsetjmphad been called during installation and alongjmpto restore that saved environment had been performed, and the recovery function as specified at that time shall be called to implement recovery from the signal raise for that thread.sig_decision_next_decider: the next decider in the sequence is called.For globally installed signal handlers, each decider function is called with a pointer to a populated
stdc_siginfo, with itsvalueset to thevalueas was specified when that decider was installed. The deciders are called in order of:
- those with
callfirst == truemost recently installed last, and then- those with
callfirst == falsein order of most recently installed first.If any decider function returns:
sig_decision_resume_execution: execution of the interrupted thread is resumed.sig_decision_call_recovery: An implementation-defined action is performed.sig_decision_next_decider: The next decider in the sequence is called.It is permitted for a signal decider to never return. Signal deciders shall meet the same requirements as for a signal handler as specified for the
signalfunction (7.14.2.1). If every signal decider returnssig_decision_next_deciderthe behavior is implementation-defined.siginstallmay be called multiple times, and for each a correspondingsiguninstallshould be present in the program. Each call tosiginstalltakes a set of signals for which the threadsafe implementation is to be activated. The threadsafe implementation shall not be deactivated for that signal number until the last uninstallation for that signal number is performed.
Recommended practice
It is recommended that pre-existing programs be upgraded to usesiginstall, and that newly written code preferssiginstalloversignal.
EXAMPLE 1: Usesigguardedto recover from aSIGFPE:
/* Recovery function for SIGFPE */ static union stdc_siginfo_value sigfpe_recovery_func(const struct stdc_siginfo *rsi) { /* Recover from the signal raise */ return rsi->value; } /* Decider function for SIGFPE */ static enum sig_decision_t sigfpe_decider_func(struct stdc_siginfo *rsi) { /* Verify we got SIGFPE */ if(rsi->signo != SIGFPE) { abort(); } rsi->value.int_value = SIGFPE; /* Please recover */ return sig_decision_call_recovery; } /* Guarded function that triggers SIGFPE via division by zero */ static union stdc_siginfo_value sigfpe_func(union stdc_siginfo_value value) { /* volatile is needed to prevent elision under optimization */ volatile int divisor = 0; /* This should trigger SIGFPE */ volatile int result = 42 / divisor; /* If we get here, this architecture doesn't trap integer divide by zero */ stdc_raise(SIGFPE, nullptr, nullptr); return value; } ... int main() { union stdc_siginfo_value value = { .int_value = 0 }; sigset_t guarded; sigemptyset(&guarded); sigaddset(&guarded, SIGFPE); value = sigguarded(&guarded, sigfpe_func, sigfpe_recovery_func, sigfpe_decider_func, value); assert(value.int_value == SIGFPE); }
The
type defined istypes defined are
stdc_siginfo_error_code_t which is
an implementation-defined complete native error code type.
Thestdc_siginfo_valueunion shall contain at least a membervoid *ptr_value, and if the implementation hasintptr_t(7.23.2.5), then also a memberintptr_t int_value, in any order.
stdc_siginfo_siginfo_t which is an
implementation-defined possibly incomplete signal information type.
stdc_siginfo_context_t which is an
implementation-defined possibly incomplete context type.
The stdc_siginfo structure shall
contain at least the following members, in any order. The semantics of
the members are expressed in the comments.
// The signal raised int signo; // The system specific error code for this signal stdc_siginfo_error_code_t error_code; // Memory location which caused fault, if appropriate void *addr; // A user-defined value union stdc_siginfo_value value; // The system specific information. Can be null if the // value was not supplied, or was supplied as null. stdc_siginfo_siginfo_t *raw_info; // The system specific context. Can be null if the // value was not supplied, or was supplied as null. stdc_siginfo_context_t *raw_context;
Thesig_func_ttype is a function type with a single argumentstdc_siginfo_valueand which returnsstdc_siginfo_value.
Thesig_recover_ttype is a function type with a single argumentconst stdc_siginfo *and which returnsstdc_siginfo_value.
The sig_decision_t enumeration shall
contain at least the following members, in any order. The semantics of
the members are expressed in the comments:
// We have decided to do nothing sig_decision_next_decider // We have fixed the cause of the signal, please resume execution sig_decision_resume_execution // thread-local signal deciders only: reset the stack and local // state to entry to `sigguarded()`, and call the recovery // function. sig_decision_call_recovery
Thesig_decide_ttype is a function type with a single argumentstdc_siginfo *and which returnssig_decision_t, this being the type of the function invocable bysigguarded()and globally installed signal deciders to decide how to handle a raised exception.
sig_atomic_t
which is the (possibly volatile-qualified) integer type of an object that can be accessed as an atomic entity, even in the presence of asynchronous interrupts.
sigset_t
which is an implementation-defined complete type able to represent a set of signals on this platform, and for which this code is valid:
sigset_t a; sigemptyset(&a); sigset_t b = a;
The macros defined are
SIG_DFL SIG_ERR SIG_IGN
which expand to constant expressions with distinct values that have type compatible with the second argument to, and the return value of, the
signalfunction, and whose values compare unequal to the address of any declarable function; and the following, which expand to positive integer constant expressions with typeintand distinct values that are the signal numbers, each corresponding to the specified condition:
SIGABRTabnormal termination, such as is initiated by theabortfunction, which is of synchronous signal category
SIGFPEan erroneous arithmetic operation, such as zero divide or an operation resulting in overflow, which is of synchronous signal category
SIGILLdetection of an invalid function image, such as an invalid instruction, which is of synchronous signal category
SIGINTreceipt of an interactive attention signal, which is of asynchronous nondebug signal category
SIGSEGVan invalid access to storage, which is of synchronous signal category
SIGTERMa termination request sent to the program, which is of asynchronous nondebug signal category
And the following macros, which restrict the optimizations which the compiler may perform:
sigfence()prevents the compiler relocating memory accesses from one side of the fence to the other side of the fence; it also causes the compiler to flush register values to memory before the fence, and to reload register values from memory after the fence. It is async-signal-safe. NOTE:atomic_signal_fence()provides weaker guarantees thansigfence(), and may be sufficient for some use cases.
sigemptyset functionSynopsis
#include <signal.h> int sigemptyset(sigset_t *setp);
Description
Calling this function is thread-safe apart from other operations concurrently acting on
*setp, and is async-signal-safe.
The set of signals pointed to by
setpis set to the empty set as defined by the implementation.
Returns
This function always returns zero.
sigfillset functionSynopsis
#include <signal.h> int sigfillset(sigset_t *setp);
Description
Calling this function is thread-safe apart from other operations concurrently acting on
*setp, and is async-signal-safe.
The set of signals pointed to by
setpis set to the full set as defined by the implementation.
Returns
This function always returns zero.
sigfillset_synchronous functionSynopsis
#include <signal.h> int sigfillset_synchronous(sigset_t *setp);
Description
Calling this function is thread-safe apart from other operations concurrently acting on
*setp, and is async-signal-safe.
*setpis set to exactly the set of synchronous signals. It is permitted for the memory pointed to bysetpto be uninitialized.
Synchronous signals are those which can be raised by a thread in the course of its execution. This set can include platform-specific additional signals, however at least these standard signals are within this set:
SIGABRT,SIGFPE,SIGILL,SIGSEGV.
Returns
This function always returns zero.
sigfillset_asynchronous_nondebug
functionSynopsis
#include <signal.h> int sigfillset_asynchronous_nondebug(sigset_t *setp);
Description
Calling this function is thread-safe apart from other operations concurrently acting on
*setp, and is async-signal-safe.
*setpis set to exactly the set of non-debug asynchronous signals. It is permitted for the memory pointed to bysetpto be uninitialized.
Non-debug asynchronous signals are those which are delivered by the system to notify the process about some event which does not default to resulting in a core dump. This set can include platform-specific additional signals, however at least these standard signals are within this set:
SIGINT,SIGTERM.
Returns
This function always returns zero.
sigfillset_asynchronous_debug
functionSynopsis
#include <signal.h> int sigfillset_asynchronous_debug(sigset_t *setp);
Description
Calling this function is thread-safe apart from other operations concurrently acting on
*setp, and is async-signal-safe.
*setpis set to exactly the set of debug asynchronous signals. It is permitted for the memory pointed to bysetpto be uninitialized.
Debug asynchronous signals are those which are delivered by the system to notify the process about some debugging event which defaults to resulting in a core dump.
Returns
This function always returns zero.
sigaddset functionSynopsis
#include <signal.h> int sigaddset(sigset_t *setp, int signo);
Description
Calling this function is thread-safe apart from other operations concurrently acting on
*setp, and is async-signal-safe.
Signal number
signois added to the set of signals pointed to bysetp, if it is not already set in which case nothing is done.
If
*setpwas not previously initialized, the behavior is undefined.
Returns
This function always returns zero.
sigdelset functionSynopsis
#include <signal.h> int sigdelset(sigset_t *setp, int signo);
Description
Calling this function is thread-safe apart from other operations concurrently acting on
*setp, and is async-signal-safe.
Signal number
signois removed from the set of signals pointed to bysetp, if it is not already unset in which case nothing is done.
If
*setpwas not previously initialized, the behavior is undefined.
Returns
This function always returns zero.
sigismember functionSynopsis
#include <signal.h> int sigismember(const sigset_t *setp, int signo);
Description
Calling this function is thread-safe apart from other operations concurrently acting on
*setp, and async-signal-safe.
If
*setpwas not previously initialized, the behavior is undefined.
Returns
If signal number
signois set within the set of signals pointed to bysetp, positive one is returned. If it is not present, zero is returned.
…
Description
…
5 If the signal occurs other than as the result of calling the
abortorraisefunction, the behavior is undefined if the signal handler refers to any object with static or thread storage duration that is not a lock-free atomic object and that is not declared with theor the signal handler calls any function in the standard library not described as async-signal-safe other than:constexprstorage-class specifier other than by assigning a value to an object declared asvolatile sig_atomic_t, or the signal handler calls any function in the standard library other than
- the
abortfunction,- the
_Exitfunction,- the
quick_exitfunction,- the functions in
<stdatomic.h>(except where explicitly stated otherwise) when the atomic arguments are lock-free,- the
atomic_is_lock_freefunction with any atomic argument, or- the
signalfunction with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler. Furthermore, if such a call to the signal function results in aSIG_ERRreturn, the object designated byerrnohas an indeterminate representation.
6 …
7 Use of this function in a multi-threaded program results in undefined behavior.
The implementation shall behave as if no library function calls the signal function.
siginstall functionSynopsis
#include <signal.h> void *siginstall(const sigset_t *guarded);
Description
Calling this function is thread-safe.
If
*guardedwas not previously initialized, the behavior is undefined.
For all signals in the signal set
guarded, the threadsafe implementation shall be activated according to the Introduction above.
Returns
If the installation was unsuccessful, this function returns a null pointer.
If the installation was successful, this function returns a handle to this installation which can be later passed to
siguninstall().
siguninstall functionSynopsis
#include <signal.h> int siguninstall(void *handle);
Description
Calling this function is thread-safe.
For all signals in the signal set originally installed by the
siginstall()which returnedhandle, the threadsafe implementation shall be deactivated according to the Introduction above.
Returns
If successful, this function returns zero. If unsuccessful, this function returns a nonzero value.
signal_decider_create functionSynopsis
#include <signal.h> void *signal_decider_create(const sigset_t *guarded, bool callfirst, sig_decide_t decider, union stdc_siginfo_value value);
Description
Calling this function is thread-safe.
Installs a global signal continuation decider function, which shall be async signal handler safe. See Introduction for how global signal continuation decider functions are called.
If
callfirstis true, installs the function at the top of the list to be called before any other functions currently in the list, otherwise it is installed at the end of the list.
Returns
If successful, this function returns a non-null pointer which can be later passed to the
signal_decider_destroy()function. Otherwise a null pointer is returned.
signal_decider_destroy
functionSynopsis
#include <signal.h> int signal_decider_destroy(void *handle);
Description
Calling this function is thread-safe.
Uninstalls a previously installed global signal continuation decider function.
Returns
If successful, this function returns zero. If unsuccessful, this function returns a nonzero value.
stdc_raise functionSynopsis
#include <signal.h> bool stdc_raise(int signo, stdc_siginfo_siginfo_t *raw_info, stdc_siginfo_context_t *raw_context);
Description
Calling this function is thread-safe and async-signal-safe.
As-if call
raise, but with the added informationraw_infoandraw_context.
Note to implementers: For your information the reference
implementation library does not raise a real signal on POSIX, but
simulates raising one instead because there is no other way to pass in a
custom siginfo_t and ucontext_t. If it reaches the end of all
lists, it calls the signal handler which was installed before threadsafe
signals was installed. On Microsoft Windows, it does actually raise a
real Win32 exception as for those you can specify a custom EXCEPTION_RECORD and CONTEXT. As both thread local and globally
installed signal handlers are directly installed with Windows, it will
perform its default action when it runs out of handlers. Suggestion to
implementers: I think it would be preferable if a real signal was
initially raised where possible, then debuggers get notified. You may be
able to persuade your standard C library to implement this e.g. on Linux
one can use syscall rt_tgsigqueueinfo.
Returns
It is implementation-defined if this function ever returns, but if it does, this function returns true if at least one signal decider installed under this facility was called.
sigguarded functionSynopsis
#include <signal.h> union stdc_siginfo_value sigguarded(const sigset_t *signals, sig_func_t guarded, sig_recover_t recovery, sig_decide_t decider, union stdc_siginfo_value value);
Description
Calling this function is thread-safe and async-signal-safe. The
deciderfunction shall be async-signal-safe.
Installs a thread-local signal continuation decider function, recording the current stack and local state such that it can be restored later if this recovery function is ever called. See 7.14.1 for how thread-local signal continuation decider functions are called.
Returns
If no signal was raised, this function returns the value returned by
guarded. If a signal was raised and a signal decider initiated recovery, this function returns the value returned byrecovery.
abort functionThe
Calling this function is async-signal-safe.abortfunction causes abnormal program termination to occur, unless the signalSIGABRTis being caught and the signal handler does not return. Whether open streams with unwritten buffered data are flushed, open streams are closed, or temporary files are removed is implementation-defined. An implementation-defined form of the status unsuccessful termination is returned to the host environment by means of the function callraise(SIGABRT).
_Exit functionThe
Calling this function is async-signal-safe._Exitfunction causes normal program termination to occur and control to be returned to the host environment. No functions registered by theatexitfunction, theat_quick_exitfunction, or signal handlers registered by thesignalfunction are called. The status returned to the host environment is determined in the same way as for theexitfunction. Whether open streams with unwritten buffered data are flushed, open streams are closed, or temporary files are removed is implementation-defined.
quick_exit functionThe
Calling this function is async-signal-safe.quick_exitfunction causes normal program termination to occur. No functions registered by theatexitfunction or signal handlers registered by the signal function are called. If a program calls thequick_exitfunction more than once, or calls theexitfunction in addition to thequick_exitfunction, the behavior is undefined. If a signal is raised while thequick_exitfunction is executing, the behavior is undefined.
<threads.h>
tss_async_signal_safewhich is a complete object type that holds an identifier for an async-signal-safe thread-specific storage pointer.
The
tss_async_signal_safe_attrstructure shall contain at least the following members, in any order. The semantics of the members are expressed in the comments.// Create an instance int (*create)(void **dest); // Destroy an instance int (*destroy)(void *v);
tss_async_signal_safe_create functionSynopsis
#include <threads.h> int tss_async_signal_safe_create(tss_async_signal_safe *val, const struct tss_async_signal_safe_attr *attr);
Description
Calling this function is thread-safe apart from other operations concurrently acting on
*val.
Creates an async-signal-safe thread-specific storage pointer. A copy of
attris taken, this describes function pointers later called to create and destroy instances of the thread-specific storage. The object pointed to byvalis set to a value that uniquely identifies the newly created instance.
Returns
This function returns
thrd_successif successful andthrd_errorif unsuccessful.
tss_async_signal_safe_destroy
functionSynopsis
#include <threads.h> int tss_async_signal_safe_destroy(tss_async_signal_safe val);
Description
Calling this function is thread-safe apart from other operations concurrently acting on
*val.
Destroys a previously created async-signal-safe thread-specific storage pointer. All thread-specific storage pointers associated with this instance are destroyed using the original
attr->destroy()upon the successful return of this function.
Returns
This function returns
thrd_successif successful andthrd_errorif unsuccessful.
tss_async_signal_safe_thread_init
functionSynopsis
#include <threads.h> int tss_async_signal_safe_thread_init(tss_async_signal_safe val);
Description
Calling this function is thread-safe.
Creates the thread-specific storage pointer for the calling thread by invoking the original
attr->create().
It is implementation-defined if the thread-specific storage pointer created has the original
attr->destroy()called upon it on thread exit, if that occurs before the call totss_async_signal_safe_destroy().
Returns
This function returns
thrd_successif successful andthrd_errorif unsuccessful.
tss_async_signal_safe_thread_get
functionSynopsis
#include <threads.h> void *tss_async_signal_safe_get(tss_async_signal_safe val);
Description
Calling this function is thread-safe and async-signal-safe.
tss_async_signal_safe_thread_init()shall have been called on the calling thread beforehand, in which case the thread-specific storage pointer created at that time is returned; otherwise the behavior is undefined.
Returns
This function returns the thread-specific storage pointer for the calling thread.
It should be noted that on some implementations, the
thread-safe alternative signal handling mechanism is the native
implementation and the signal
function is emulated using that native system. On such implementations,
signal based code will always be
second tier to the thread-safe alternative signal handling mechanism,
and they will define what happens if all handlers installed choose to
not handle a signal raise.↩︎