Skip to content

Commit

Permalink
Global signal deciders for POSIX should now be working.
Browse files Browse the repository at this point in the history
  • Loading branch information
ned14 committed Feb 12, 2025
1 parent 5a72ae4 commit b73c899
Show file tree
Hide file tree
Showing 11 changed files with 811 additions and 104 deletions.
2 changes: 1 addition & 1 deletion .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: true
BinPackParameters: true
ColumnLimit: 120
ColumnLimit: 80
CommentPragmas: '^!<'
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ContinuationIndentWidth: 0
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ enable_testing()
set(LIBRARY_SOURCES
"src/wg14_signals/current_thread_id.c"
"src/wg14_signals/tss_async_signal_safe.c"
$<$<NOT:$<PLATFORM_ID:Windows>>:src/wg14_signals/thrd_signal_handle_posix.c>
$<$<PLATFORM_ID:Windows>:src/wg14_signals/thrd_signal_handle_windows.c>

# Not mandatory and can be substituted
"src/wg14_signals/thread_atexit.cpp"
Expand Down
13 changes: 13 additions & 0 deletions include/wg14_signals/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ limitations under the License.
#endif
#endif

#ifndef WG14_SIGNALS_DEFAULT_VISIBILITY
#ifdef _WIN32
#define WG14_SIGNALS_DEFAULT_VISIBILITY
#else
#define WG14_SIGNALS_DEFAULT_VISIBILITY __attribute__((visibility("default")))
#endif
#endif

#ifndef WG14_SIGNALS_EXTERN
#if WG14_SIGNALS_SOURCE
#ifdef _WIN32
Expand All @@ -52,6 +60,11 @@ limitations under the License.
#endif
#endif

#ifndef WG14_SIGNALS_STDERR_PRINTF
#include <stdio.h>
#define WG14_SIGNALS_STDERR_PRINTF(...) fprintf(stderr, __VA_ARGS__)
#endif

#ifdef __cplusplus
extern "C"
{
Expand Down
18 changes: 12 additions & 6 deletions include/wg14_signals/current_thread_id.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,29 @@ extern "C"
//! \brief The type of a thread id
typedef uintptr_t WG14_SIGNALS_PREFIX(thread_id_t);

static const WG14_SIGNALS_PREFIX(thread_id_t) WG14_SIGNALS_PREFIX(thread_id_t_tombstone) = 0;
static const WG14_SIGNALS_PREFIX(thread_id_t)
WG14_SIGNALS_PREFIX(thread_id_t_tombstone) = 0;

#ifdef _WIN32
static
#else
WG14_SIGNALS_EXTERN
#endif
WG14_SIGNALS_THREAD_LOCAL WG14_SIGNALS_PREFIX(thread_id_t) WG14_SIGNALS_PREFIX(current_thread_id_cached);
WG14_SIGNALS_THREAD_LOCAL WG14_SIGNALS_PREFIX(thread_id_t)
WG14_SIGNALS_PREFIX(current_thread_id_cached);

WG14_SIGNALS_EXTERN WG14_SIGNALS_PREFIX(thread_id_t) WG14_SIGNALS_PREFIX(current_thread_id_cached_set)(void);
WG14_SIGNALS_EXTERN WG14_SIGNALS_PREFIX(thread_id_t)
WG14_SIGNALS_PREFIX(current_thread_id_cached_set)(void);

//! \brief THREADSAFE; ASYNC SIGNAL SAFE; Retrieve the current thread id
static WG14_SIGNALS_INLINE WG14_SIGNALS_PREFIX(thread_id_t) WG14_SIGNALS_PREFIX(current_thread_id)(void)
static WG14_SIGNALS_INLINE WG14_SIGNALS_PREFIX(thread_id_t)
WG14_SIGNALS_PREFIX(current_thread_id)(void)
{
if(WG14_SIGNALS_PREFIX(current_thread_id_cached) == WG14_SIGNALS_PREFIX(thread_id_t_tombstone))
if(WG14_SIGNALS_PREFIX(current_thread_id_cached) ==
WG14_SIGNALS_PREFIX(thread_id_t_tombstone))
{
WG14_SIGNALS_PREFIX(current_thread_id_cached) = WG14_SIGNALS_PREFIX(current_thread_id_cached_set)();
WG14_SIGNALS_PREFIX(current_thread_id_cached) =
WG14_SIGNALS_PREFIX(current_thread_id_cached_set)();
}
return WG14_SIGNALS_PREFIX(current_thread_id_cached);
}
Expand Down
188 changes: 111 additions & 77 deletions include/wg14_signals/thrd_signal_handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ limitations under the License.
#include "config.h"

#include <setjmp.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>

#ifdef __cplusplus
Expand Down Expand Up @@ -64,13 +66,16 @@ typedef int WG14_SIGNALS_PREFIX(thrd_raised_signal_error_code_t);
*/
struct WG14_SIGNALS_PREFIX(thrd_raised_signal_info)
{
jmp_buf buf; //!< setjmp() buffer written on entry to guarded section
int signo; //!< The signal raised
const jmp_buf
*buf; //!< setjmp() buffer written on entry to guarded section
int signo; //!< The signal raised

//! The system specific error code for this signal, the `si_errno` code (POSIX) or `NTSTATUS` code (Windows)
//! The system specific error code for this signal, the `si_errno` code
//! (POSIX) or `NTSTATUS` code (Windows)
WG14_SIGNALS_PREFIX(thrd_raised_signal_error_code_t) error_code;
void *addr; //!< Memory location which caused fault, if appropriate
union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value; //!< A user-defined value
union WG14_SIGNALS_PREFIX(
thrd_raised_signal_info_value) value; //!< A user-defined value

//! The OS specific `siginfo_t *` (POSIX) or `PEXCEPTION_RECORD` (Windows)
void *raw_info;
Expand All @@ -79,113 +84,142 @@ typedef int WG14_SIGNALS_PREFIX(thrd_raised_signal_error_code_t);
};

//! \brief The type of the guarded function.
typedef union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) (*WG14_SIGNALS_PREFIX(thrd_signal_func_t))(
typedef union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) (
*WG14_SIGNALS_PREFIX(thrd_signal_func_t))(
union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value));

//! \brief The type of the function called to recover from a signal being raised in a guarded section.
typedef union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) (*WG14_SIGNALS_PREFIX(thrd_signal_recover_t))(
//! \brief The type of the function called to recover from a signal being
//! raised in a guarded section.
typedef union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) (
*WG14_SIGNALS_PREFIX(thrd_signal_recover_t))(
const struct WG14_SIGNALS_PREFIX(thrd_raised_signal_info) *);

//! \brief The type of the function called when a signal is raised. Returns true to continue guarded code, false to
//! recover.
typedef bool (*WG14_SIGNALS_PREFIX(thrd_signal_decide_t))(struct WG14_SIGNALS_PREFIX(thrd_raised_signal_info) *);
//! \brief The type of the function called when a signal is raised. Returns
//! true to continue guarded code, false to recover.
typedef bool (*WG14_SIGNALS_PREFIX(thrd_signal_decide_t))(
struct WG14_SIGNALS_PREFIX(thrd_raised_signal_info) *);

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4190) // C-linkage with UDTs
#endif
/*! \brief Installs a thread-local signal guard for the calling thread, and calls the guarded function `guarded`.
/*! \brief Installs a thread-local signal guard for the calling thread, and
calls the guarded function `guarded`.
\return The value returned by `guarded`, or `recovery`.
\param signals The set of signals to guard against.
\param guarded A function whose execution is to be guarded against signal raises.
\param guarded A function whose execution is to be guarded against signal
raises.
\param recovery A function to be called if a signal is raised.
\param decider A function to be called to decide whether to recover from the signal and continue
the execution of the guarded routine, or to abort and call the recovery routine.
\param decider A function to be called to decide whether to recover from the
signal and continue the execution of the guarded routine, or to abort and call
the recovery routine.
\param value A value to supply to the guarded routine.
*/
WG14_SIGNALS_EXTERN union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value)
WG14_SIGNALS_PREFIX(thrd_signal_call)(const sigset_t *signals, WG14_SIGNALS_PREFIX(thrd_signal_func_t) guarded,
WG14_SIGNALS_PREFIX(thrd_signal_recover_t) recovery,
WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider,
union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value);
WG14_SIGNALS_PREFIX(thrd_signal_invoke)(
const sigset_t *signals, WG14_SIGNALS_PREFIX(thrd_signal_func_t) guarded,
WG14_SIGNALS_PREFIX(thrd_signal_recover_t) recovery,
WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider,
union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value);
#ifdef _MSC_VER
#pragma warning(pop)
#endif

/*! \brief Call the currently installed signal handler for a signal (POSIX), or raise a Win32 structured
exception (Windows), returning false if no handler was called due to the currently
installed handler being `SIG_IGN` (POSIX).
Note that on POSIX, we fetch the currently installed signal handler and try to call it directly.
This allows us to supply custom `raw_info` and `raw_context`, and we do all the things which the signal
handler flags tell us to do beforehand [1]. If the current handler has been defaulted, we
enable the signal and execute `pthread_kill(pthread_self(), signo)` in order to invoke the
default handling.
/*! \brief Call OUR currently installed signal decider for a signal (POSIX),
or raise a Win32 structured exception (Windows), returning false if we have no
decider installed for that signal.
Note that on POSIX, we fetch OUR currently installed signal decider and call
it directly. This allows us to supply custom `raw_info` and `raw_context`.
Each decider in our chain will be invoked in turn until we reach whatever the
signal handler was when this library was first initialised, and we hand off
to that handler. If that handler was defaulted and the default handling is not
to ignore, we reset the handler installation and execute
`pthread_kill(pthread_self(), signo)` in order to invoke the default handling.
It is important to note that this call does not raise signals itself except in
that final handling step as just described. Therefore, if your code overwrites
the signal handlers installed by this library with a custom handler, and you
wish to pass on signal handling to this library, this is the right API to call
to do that.
Note that on Windows, `raw_context` is ignored as there is no way to override
the context thrown with a Win32 structured exception.
*/
WG14_SIGNALS_EXTERN bool
WG14_SIGNALS_PREFIX(thrd_signal_raise)(int signo, void *raw_info,
void *raw_context);

Note that on Windows, `raw_context` is ignored as there is no way to override the context thrown
with a Win32 structured exception.
/*! \brief THREADSAFE On platforms where it is necessary (POSIX), installs,
and potentially enables, the global signal handlers for the signals specified
by `guarded`. Each signal installed is threadsafe reference counted, so this
is safe to call from multiple threads or instantiate multiple times.
[1]: We currently do not implement alternative stack switching. If a handler requests that, we
simply abort the process. Code donations implementing support are welcome.
*/
WG14_SIGNALS_EXTERN bool WG14_SIGNALS_PREFIX(thrd_signal_raise)(int signo, void *raw_info, void *raw_context);
On platforms with better than POSIX global signal support, this function does
nothing.
/*! \brief THREADSAFE On platforms where it is necessary (POSIX), installs, and potentially enables,
the global signal handlers for the signals specified by `guarded`. Each signal installed
is threadsafe reference counted, so this is safe to call from multiple threads or instantiate
multiple times.
On platforms with better than POSIX global signal support, this function does nothing.
If `guarded` is null, all the standard POSIX signals are used. `version` is
reserved for future use, and should be zero.
## POSIX only
Any existing global signal handlers are replaced with a filtering signal handler, which
checks if the current kernel thread has installed a signal guard, and if so executes the
guard. If no signal guard has been installed for the current kernel thread, global signal
continuation handlers are executed. If none claims the signal, the previously
installed signal handler is called.
After the new signal handlers have been installed, the guarded signals are globally enabled
for all threads of execution. Be aware that the handlers are installed with `SA_NODEFER`
to avoid the need to perform an expensive syscall when a signal is handled.
However this may also produce surprise e.g. infinite loops.
\warning This class is threadsafe with respect to other concurrent executions of itself,
but is NOT threadsafe with respect to other code modifying the global signal handlers.
Any existing global signal handlers are replaced with a filtering signal
handler, which checks if the current kernel thread has installed a signal
guard, and if so executes the guard. If no signal guard has been installed for
the current kernel thread, global signal continuation handlers are executed.
If none claims the signal, the previously installed signal handler is called.
After the new signal handlers have been installed, the guarded signals are
globally enabled for all threads of execution. Be aware that the handlers are
installed with `SA_NODEFER` to avoid the need to perform an expensive syscall
when a signal is handled. However this may also produce surprise e.g. infinite
loops.
\warning This class is threadsafe with respect to other concurrent executions
of itself, but is NOT threadsafe with respect to other code modifying the
global signal handlers.
*/
WG14_SIGNALS_EXTERN void *WG14_SIGNALS_PREFIX(modern_signals_install)(const sigset_t *guarded, int version);
WG14_SIGNALS_EXTERN void *
WG14_SIGNALS_PREFIX(modern_signals_install)(const sigset_t *guarded,
int version);
/*! \brief THREADSAFE Uninstall a previously installed signal guard.
*/
WG14_SIGNALS_EXTERN bool WG14_SIGNALS_PREFIX(modern_signals_uninstall)(void *i);
WG14_SIGNALS_EXTERN bool
WG14_SIGNALS_PREFIX(modern_signals_uninstall)(void *i);
/*! \brief THREADSAFE Uninstall a previously system installed signal guard.
*/
WG14_SIGNALS_EXTERN bool WG14_SIGNALS_PREFIX(modern_signals_uninstall_system)(int version);

/*! \brief THREADSAFE NOT REENTRANT Create a global signal continuation decider. Threadsafe with respect to
other calls of this function, but not reentrant i.e. modifying the global signal continuation
decider registry whilst inside a global signal continuation decider is racy. Called after
all thread local handling is exhausted. Note that what you can safely do in the decider
function is extremely limited, only async signal safe functions may be called.
\return An opaque pointer to the registered decider. `NULL` if `malloc` failed.
WG14_SIGNALS_EXTERN bool
WG14_SIGNALS_PREFIX(modern_signals_uninstall_system)(int version);

/*! \brief THREADSAFE NOT REENTRANT Create a global signal continuation
decider. Threadsafe with respect to other calls of this function, but not
reentrant i.e. modifying the global signal continuation decider registry
whilst inside a global signal continuation decider is racy. Called after all
thread local handling is exhausted. Note that what you can safely do in the
decider function is extremely limited, only async signal safe functions may be
called.
\return An opaque pointer to the registered decider. `NULL` if `malloc`
failed.
\param guarded The set of signals to be guarded against.
\param callfirst True if this decider should be called before any other. Otherwise
call order is in the order of addition.
\param decider A decider function, which must return `true` if execution is to resume,
`false` if the next decider function should be called.
\param value A user supplied value to set in the `raised_signal_info` passed to the
decider callback.
\param callfirst True if this decider should be called before any other.
Otherwise call order is in the order of addition.
\param decider A decider function, which must return `true` if execution is to
resume, `false` if the next decider function should be called.
\param value A user supplied value to set in the `raised_signal_info` passed
to the decider callback.
*/
WG14_SIGNALS_EXTERN void *
WG14_SIGNALS_PREFIX(signal_decider_create)(const sigset_t *guarded, bool callfirst,
WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider,
union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value);
/*! \brief THREADSAFE NOT REENTRANT Destroy a global signal continuation decider. Threadsafe with
respect to other calls of this function, but not reentrant i.e. do not call
whilst inside a global signal continuation decider.
WG14_SIGNALS_EXTERN void *WG14_SIGNALS_PREFIX(signal_decider_create)(
const sigset_t *guarded, bool callfirst,
WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider,
union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value);
/*! \brief THREADSAFE NOT REENTRANT Destroy a global signal continuation
decider. Threadsafe with respect to other calls of this function, but not
reentrant i.e. do not call whilst inside a global signal continuation decider.
\return True if recognised and thus removed.
*/
WG14_SIGNALS_EXTERN bool WG14_SIGNALS_PREFIX(signal_decider_destroy)(void *decider);
WG14_SIGNALS_EXTERN bool
WG14_SIGNALS_PREFIX(signal_decider_destroy)(void *decider);


#ifdef __cplusplus
Expand Down
31 changes: 19 additions & 12 deletions include/wg14_signals/tss_async_signal_safe.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ extern "C"
#endif

//! \brief The type of an async signal safe thread local
typedef struct WG14_SIGNALS_PREFIX(tss_async_signal_safe) * WG14_SIGNALS_PREFIX(tss_async_signal_safe);
typedef struct WG14_SIGNALS_PREFIX(tss_async_signal_safe) *
WG14_SIGNALS_PREFIX(tss_async_signal_safe);

//! \brief The attributes for creating an async signal safe thread local
struct WG14_SIGNALS_PREFIX(tss_async_signal_safe_attr)
Expand All @@ -43,22 +44,28 @@ extern "C"
const struct WG14_SIGNALS_PREFIX(tss_async_signal_safe_attr) * attr);

//! \brief Destroy an async signal safe thread local instance
WG14_SIGNALS_EXTERN int WG14_SIGNALS_PREFIX(tss_async_signal_safe_destroy)(WG14_SIGNALS_PREFIX(tss_async_signal_safe)
val);
WG14_SIGNALS_EXTERN int WG14_SIGNALS_PREFIX(tss_async_signal_safe_destroy)(
WG14_SIGNALS_PREFIX(tss_async_signal_safe) val);

/*! \brief THREADSAFE Initialise an async signal safe thread local instance for a specific thread
/*! \brief THREADSAFE Initialise an async signal safe thread local instance
for a specific thread
The initialisation and registration of a thread local state for a specific thread is not async signal safe, and so
must be manually performed by each thread before use of an async signal safe thread local instance. Call this function
somewhere near the beginning of your thread instance.
The initialisation and registration of a thread local state for a specific
thread is not async signal safe, and so must be manually performed by each
thread before use of an async signal safe thread local instance. Call this
function somewhere near the beginning of your thread instance.
It is safe to call this functions many times in a thread.
*/
WG14_SIGNALS_EXTERN int
WG14_SIGNALS_PREFIX(tss_async_signal_safe_thread_init)(WG14_SIGNALS_PREFIX(tss_async_signal_safe) val);
WG14_SIGNALS_EXTERN int WG14_SIGNALS_PREFIX(
tss_async_signal_safe_thread_init)(WG14_SIGNALS_PREFIX(tss_async_signal_safe)
val);

/*! \brief THREADSAFE ASYNC-SIGNAL-SAFE Get the thread local value for the current thread.
/*! \brief THREADSAFE ASYNC-SIGNAL-SAFE Get the thread local value for the
* current thread.
*/
WG14_SIGNALS_EXTERN void *WG14_SIGNALS_PREFIX(tss_async_signal_safe_get)(WG14_SIGNALS_PREFIX(tss_async_signal_safe)
val);
WG14_SIGNALS_EXTERN void *WG14_SIGNALS_PREFIX(tss_async_signal_safe_get)(
WG14_SIGNALS_PREFIX(tss_async_signal_safe) val);

#ifdef __cplusplus
}
Expand Down
Loading

0 comments on commit b73c899

Please sign in to comment.