mirror of
https://https.git.savannah.gnu.org/git/gnulib.git
synced 2026-04-28 06:33:36 +00:00
sigprocmask: Support multithreaded applications on native Windows.
* lib/signal.in.h (WIN_PTHREADS_SIGNAL_H): New macro. * lib/sigprocmask.c: Include windows-spin.h. (thread_local): New macro. (blocked_set, pending_array): Mark as thread-local. (blocked_handler): Remove function. (struct override): New type. (overrides, overrides_lock): New variables. (override_handler): New function. (pthread_sigmask): New function, borrowing from the previous sigprocmask definition. (sigprocmask): Now a wrapper around pthread_sigmask. (rpl_signal): Use the overrides_lock to make it multithread-safe. (_gl_raise_SIGPIPE): Add comments. * modules/sigprocmask (Depends-on): Add windows-spin. * lib/pthread_sigmask.c: Revert last change. On native Windows, don't define pthread_sigmask here. * modules/pthread_sigmask (Depends-on): Remove lock. * NEWS: Remove the last entry.
This commit is contained in:
22
ChangeLog
22
ChangeLog
@@ -1,3 +1,25 @@
|
||||
2026-04-10 Bruno Haible <bruno@clisp.org>
|
||||
|
||||
sigprocmask: Support multithreaded applications on native Windows.
|
||||
* lib/signal.in.h (WIN_PTHREADS_SIGNAL_H): New macro.
|
||||
* lib/sigprocmask.c: Include windows-spin.h.
|
||||
(thread_local): New macro.
|
||||
(blocked_set, pending_array): Mark as thread-local.
|
||||
(blocked_handler): Remove function.
|
||||
(struct override): New type.
|
||||
(overrides, overrides_lock): New variables.
|
||||
(override_handler): New function.
|
||||
(pthread_sigmask): New function, borrowing from the previous sigprocmask
|
||||
definition.
|
||||
(sigprocmask): Now a wrapper around pthread_sigmask.
|
||||
(rpl_signal): Use the overrides_lock to make it multithread-safe.
|
||||
(_gl_raise_SIGPIPE): Add comments.
|
||||
* modules/sigprocmask (Depends-on): Add windows-spin.
|
||||
* lib/pthread_sigmask.c: Revert last change. On native Windows, don't
|
||||
define pthread_sigmask here.
|
||||
* modules/pthread_sigmask (Depends-on): Remove lock.
|
||||
* NEWS: Remove the last entry.
|
||||
|
||||
2026-04-06 Bruno Haible <bruno@clisp.org>
|
||||
|
||||
stat: Update doc regarding mingw bug.
|
||||
|
||||
6
NEWS
6
NEWS
@@ -78,12 +78,6 @@ User visible incompatible changes
|
||||
|
||||
Date Modules Changes
|
||||
|
||||
2026-04-05 sigprocmask The native MS-Windows implementation no longer
|
||||
tries to be thread-safe. (As per POSIX,
|
||||
sigprocmask's behavior has never been
|
||||
well-defined in multithreaded programs,
|
||||
which should use pthread_sigmask instead.)
|
||||
|
||||
2026-03-31 lock This module no longer defines the once-only
|
||||
execution primitives. For these, request the
|
||||
'once' module and #include "glthread/once.h".
|
||||
|
||||
@@ -19,39 +19,22 @@
|
||||
/* Specification. */
|
||||
#include <signal.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
/* The native Windows implementation is defined in sigprocmask.c. */
|
||||
#if !(defined _WIN32 && !defined __CYGWIN__)
|
||||
|
||||
#if PTHREAD_SIGMASK_INEFFECTIVE
|
||||
# include <string.h>
|
||||
#endif
|
||||
# include <errno.h>
|
||||
# include <stddef.h>
|
||||
|
||||
#if !HAVE_SIGPROCMASK && !GNULIB_PTHREAD_SIGMASK_SINGLE_THREAD
|
||||
# include "glthread/lock.h"
|
||||
|
||||
gl_lock_define_initialized (static, sig_lock)
|
||||
|
||||
/* A thread-safe variant of the Gnulib sigprocmask substitute.
|
||||
Unfortunately it still has undesirable effects in multithreaded processes,
|
||||
as it can stomp on other threads' signal masks. */
|
||||
static int
|
||||
sigprocmask_r (int how, sigset_t const *new_mask, sigset_t *old_mask)
|
||||
{
|
||||
gl_lock_lock (sig_lock);
|
||||
int ret = sigprocmask (how, new_mask, old_mask);
|
||||
gl_lock_unlock (sig_lock);
|
||||
return ret;
|
||||
}
|
||||
# undef sigprocmask
|
||||
# define sigprocmask sigprocmask_r
|
||||
#endif
|
||||
# if PTHREAD_SIGMASK_INEFFECTIVE
|
||||
# include <string.h>
|
||||
# endif
|
||||
|
||||
int
|
||||
pthread_sigmask (int how, const sigset_t *new_mask, sigset_t *old_mask)
|
||||
#undef pthread_sigmask
|
||||
# undef pthread_sigmask
|
||||
{
|
||||
#if HAVE_PTHREAD_SIGMASK && !PTHREAD_SIGMASK_NOT_IN_LIBC
|
||||
# if PTHREAD_SIGMASK_INEFFECTIVE
|
||||
# if HAVE_PTHREAD_SIGMASK && !PTHREAD_SIGMASK_NOT_IN_LIBC
|
||||
# if PTHREAD_SIGMASK_INEFFECTIVE
|
||||
sigset_t omask;
|
||||
sigset_t *old_mask_ptr = &omask;
|
||||
sigemptyset (&omask);
|
||||
@@ -60,13 +43,13 @@ pthread_sigmask (int how, const sigset_t *new_mask, sigset_t *old_mask)
|
||||
sigaddset (&omask, SIGILL);
|
||||
sigset_t omask_copy;
|
||||
memcpy (&omask_copy, &omask, sizeof omask);
|
||||
# else
|
||||
# else
|
||||
sigset_t *old_mask_ptr = old_mask;
|
||||
# endif
|
||||
# endif
|
||||
|
||||
int ret = pthread_sigmask (how, new_mask, old_mask_ptr);
|
||||
|
||||
# if PTHREAD_SIGMASK_INEFFECTIVE
|
||||
# if PTHREAD_SIGMASK_INEFFECTIVE
|
||||
if (ret == 0)
|
||||
{
|
||||
/* Detect whether pthread_sigmask is currently ineffective.
|
||||
@@ -84,14 +67,16 @@ pthread_sigmask (int how, const sigset_t *new_mask, sigset_t *old_mask)
|
||||
if (old_mask)
|
||||
memcpy (old_mask, &omask, sizeof omask);
|
||||
}
|
||||
# endif
|
||||
# if PTHREAD_SIGMASK_FAILS_WITH_ERRNO
|
||||
# endif
|
||||
# if PTHREAD_SIGMASK_FAILS_WITH_ERRNO
|
||||
if (ret == -1)
|
||||
return errno;
|
||||
# endif
|
||||
# endif
|
||||
return ret;
|
||||
#else
|
||||
# else
|
||||
int ret = sigprocmask (how, new_mask, old_mask);
|
||||
return (ret < 0 ? errno : 0);
|
||||
#endif
|
||||
# endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
#endif
|
||||
@PRAGMA_COLUMNS@
|
||||
|
||||
/* Deactivate the mingw <pthread_signal.h>, that provides an unusable definition
|
||||
of pthread_sigmask(). We need to do this before including <signal.h>. */
|
||||
#ifndef WIN_PTHREADS_SIGNAL_H
|
||||
#define WIN_PTHREADS_SIGNAL_H
|
||||
#endif
|
||||
|
||||
#if defined __need_sig_atomic_t || defined __need_sigset_t || defined _@GUARD_PREFIX@_ALREADY_INCLUDING_SIGNAL_H || (defined _SIGNAL_H && !defined __SIZEOF_PTHREAD_MUTEX_T)
|
||||
/* Special invocation convention:
|
||||
- Inside glibc header files.
|
||||
|
||||
@@ -28,11 +28,14 @@
|
||||
# include "msvc-inval.h"
|
||||
#endif
|
||||
|
||||
#include "windows-spin.h"
|
||||
|
||||
/* We assume that a platform without POSIX signal blocking functions
|
||||
also does not have the POSIX sigaction() function, only the
|
||||
signal() function. We also assume signal() has SysV semantics,
|
||||
where any handler is uninstalled prior to being invoked. This is
|
||||
true for native Windows platforms. */
|
||||
true for native Windows platforms. Documentation:
|
||||
<https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/signal> */
|
||||
|
||||
/* We use raw signal(), but also provide a wrapper rpl_signal() so
|
||||
that applications can query or change a blocked signal. */
|
||||
@@ -181,25 +184,21 @@ sigfillset (sigset_t *set)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Storage class specifier for thread-local storage. */
|
||||
#undef thread_local
|
||||
#if defined _MSC_VER
|
||||
/* <https://learn.microsoft.com/en-us/cpp/parallel/thread-local-storage-tls> */
|
||||
# define thread_local __declspec(thread)
|
||||
#else
|
||||
/* <https://gcc.gnu.org/onlinedocs/gcc/Thread-Local.html> */
|
||||
# define thread_local __thread
|
||||
#endif
|
||||
|
||||
/* Set of currently blocked signals. */
|
||||
static sigset_t blocked_set /* = 0 */;
|
||||
static thread_local sigset_t blocked_set /* = 0 */;
|
||||
|
||||
/* Set of currently blocked and pending signals. */
|
||||
static volatile sig_atomic_t pending_array[NSIG] /* = { 0 } */;
|
||||
|
||||
/* Signal handler that is installed for blocked signals. */
|
||||
static void
|
||||
blocked_handler (int sig)
|
||||
{
|
||||
/* Reinstall the handler, in case the signal occurs multiple times
|
||||
while blocked. There is an inherent race where an asynchronous
|
||||
signal in between when the kernel uninstalled the handler and
|
||||
when we reinstall it will trigger the default handler; oh
|
||||
well. */
|
||||
signal (sig, blocked_handler);
|
||||
if (sig >= 0 && sig < NSIG)
|
||||
pending_array[sig] = 1;
|
||||
}
|
||||
static thread_local volatile sig_atomic_t pending_array[NSIG] /* = { 0 } */;
|
||||
|
||||
int
|
||||
sigpending (sigset_t *set)
|
||||
@@ -213,12 +212,61 @@ sigpending (sigset_t *set)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The previous signal handlers.
|
||||
Only the array elements corresponding to blocked signals are relevant. */
|
||||
static volatile handler_t old_handlers[NSIG];
|
||||
/* A registry which signal handlers are overridden. */
|
||||
struct override
|
||||
{
|
||||
/* True if the signal is or was blocked in some thread. */
|
||||
volatile int overridden;
|
||||
/* The original signal handler, if overridden. */
|
||||
volatile handler_t original_handler;
|
||||
};
|
||||
static struct override overrides[NSIG] /* = { { 0, NULL }, ... } */;
|
||||
|
||||
/* A spin lock that protects overrides against simultaneous use from
|
||||
different threads. */
|
||||
static glwthread_spinlock_t overrides_lock = GLWTHREAD_SPIN_INIT;
|
||||
|
||||
/* Signal handler that overrides an original one. */
|
||||
static void
|
||||
override_handler (int sig)
|
||||
{
|
||||
/* Reinstall the handler, in case the signal occurs multiple times
|
||||
while blocked. There is an inherent race where an asynchronous
|
||||
signal in between when the kernel uninstalled the handler and
|
||||
when we reinstall it will trigger the default handler; oh well. */
|
||||
signal (sig, override_handler);
|
||||
|
||||
if ((blocked_set >> sig) & 1)
|
||||
{
|
||||
/* The signal is blocked in the current thread. */
|
||||
pending_array[sig] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!overrides[sig].overridden)
|
||||
{
|
||||
/* Another thread has already installed the override_handler but
|
||||
is not yet done. Wait until it has finished the operation. */
|
||||
glwthread_spin_lock (&overrides_lock);
|
||||
glwthread_spin_unlock (&overrides_lock);
|
||||
/* Now overrides[sig].overridden must be true. */
|
||||
if (!overrides[sig].overridden)
|
||||
abort ();
|
||||
}
|
||||
handler_t handler = overrides[sig].original_handler;
|
||||
if (handler == SIG_DFL)
|
||||
{
|
||||
/* Restore the original handler. Then raise the signal again. */
|
||||
signal (sig, SIG_DFL);
|
||||
raise (sig);
|
||||
}
|
||||
else if (handler != SIG_IGN)
|
||||
(*handler) (sig);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
sigprocmask (int operation, const sigset_t *set, sigset_t *old_set)
|
||||
pthread_sigmask (int operation, const sigset_t *set, sigset_t *old_set)
|
||||
{
|
||||
if (old_set != NULL)
|
||||
*old_set = blocked_set;
|
||||
@@ -238,8 +286,7 @@ sigprocmask (int operation, const sigset_t *set, sigset_t *old_set)
|
||||
new_blocked_set = blocked_set & ~*set;
|
||||
break;
|
||||
default:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
sigset_t to_unblock = blocked_set & ~new_blocked_set;
|
||||
@@ -249,9 +296,35 @@ sigprocmask (int operation, const sigset_t *set, sigset_t *old_set)
|
||||
for (int sig = 0; sig < NSIG; sig++)
|
||||
if ((to_block >> sig) & 1)
|
||||
{
|
||||
pending_array[sig] = 0;
|
||||
if ((old_handlers[sig] = signal (sig, blocked_handler)) != SIG_ERR)
|
||||
blocked_set |= 1U << sig;
|
||||
/* All pending_array[sig] remain zero. */
|
||||
blocked_set |= 1U << sig;
|
||||
if (!overrides[sig].overridden)
|
||||
{
|
||||
glwthread_spin_lock (&overrides_lock);
|
||||
if (!overrides[sig].overridden)
|
||||
{
|
||||
/* Now it's OK to install the override_handler:
|
||||
- If it gets invoked in this thread, there is no race
|
||||
condition since blocked_set already has the sig bit
|
||||
set.
|
||||
- If it gets invoked in another thread, the
|
||||
overrides_lock protects it from proceeding until we
|
||||
have stored the old_handler. */
|
||||
handler_t old_handler = signal (sig, override_handler);
|
||||
if (old_handler != SIG_ERR)
|
||||
{
|
||||
if (old_handler == override_handler)
|
||||
/* Different threads calling pthread_sigmask at the
|
||||
same time (race condition). This shouldn't
|
||||
happen, thanks to the second test of
|
||||
overrides[sig].overridden, above. */
|
||||
abort ();
|
||||
overrides[sig].original_handler = old_handler;
|
||||
overrides[sig].overridden = 1;
|
||||
}
|
||||
}
|
||||
glwthread_spin_unlock (&overrides_lock);
|
||||
}
|
||||
}
|
||||
|
||||
if (to_unblock != 0)
|
||||
@@ -261,11 +334,6 @@ sigprocmask (int operation, const sigset_t *set, sigset_t *old_set)
|
||||
for (int sig = 0; sig < NSIG; sig++)
|
||||
if ((to_unblock >> sig) & 1)
|
||||
{
|
||||
if (signal (sig, old_handlers[sig]) != blocked_handler)
|
||||
/* The application changed a signal handler while the signal
|
||||
was blocked, bypassing our rpl_signal replacement.
|
||||
We don't support this. */
|
||||
abort ();
|
||||
received[sig] = pending_array[sig];
|
||||
blocked_set &= ~(1U << sig);
|
||||
pending_array[sig] = 0;
|
||||
@@ -282,8 +350,24 @@ sigprocmask (int operation, const sigset_t *set, sigset_t *old_set)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* sigprocmask is like pthread_sigmask, except that it has undefined behaviour
|
||||
in multithreaded applications and a different return value convention. */
|
||||
int
|
||||
sigprocmask (int operation, const sigset_t *set, sigset_t *old_set)
|
||||
{
|
||||
int ret = pthread_sigmask (operation, set, old_set);
|
||||
if (ret == 0)
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
errno = ret;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Install the handler FUNC for signal SIG, and return the previous
|
||||
handler. */
|
||||
handler.
|
||||
This override transparently hides the override_handler. */
|
||||
handler_t
|
||||
rpl_signal (int sig, handler_t handler)
|
||||
{
|
||||
@@ -297,23 +381,31 @@ rpl_signal (int sig, handler_t handler)
|
||||
sig = SIGABRT;
|
||||
#endif
|
||||
|
||||
if (blocked_set & (1U << sig))
|
||||
handler_t result;
|
||||
if (overrides[sig].overridden)
|
||||
{
|
||||
/* POSIX states that sigprocmask and signal are both
|
||||
async-signal-safe. This is not true of our
|
||||
implementation - there is a slight data race where an
|
||||
asynchronous interrupt on signal A can occur after we
|
||||
install blocked_handler but before we have updated
|
||||
old_handlers for signal B, such that handler A can see
|
||||
stale information if it calls signal(B). Oh well -
|
||||
signal handlers really shouldn't try to manipulate the
|
||||
installed handlers of unrelated signals. */
|
||||
handler_t result = old_handlers[sig];
|
||||
old_handlers[sig] = handler;
|
||||
return result;
|
||||
/* There is an inherent race condition, when one thread calls
|
||||
rpl_signal to install a different signal handler, while another
|
||||
thread invokes the signal handler (via override_handler).
|
||||
It is unavoidable. Nothing we can do about it. */
|
||||
result = overrides[sig].original_handler;
|
||||
overrides[sig].original_handler = handler;
|
||||
}
|
||||
else
|
||||
return signal (sig, handler);
|
||||
{
|
||||
/* Lock, in case of another thread calling pthread_sigmask, with the
|
||||
effect of installing the override_handler (race condition). */
|
||||
glwthread_spin_lock (&overrides_lock);
|
||||
if (overrides[sig].overridden)
|
||||
{
|
||||
result = overrides[sig].original_handler;
|
||||
overrides[sig].original_handler = handler;
|
||||
}
|
||||
else
|
||||
result = signal (sig, handler);
|
||||
glwthread_spin_unlock (&overrides_lock);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -327,7 +419,12 @@ rpl_signal (int sig, handler_t handler)
|
||||
int
|
||||
_gl_raise_SIGPIPE (void)
|
||||
{
|
||||
/* On POSIX platforms, SIGPIPE is generated by the kernel and delivered to
|
||||
any thread of the current process. In the SIGPIPE emulation here, we do
|
||||
it slightly differently: we deliver it to the current thread always,
|
||||
like raise (SIGPIPE) would do. */
|
||||
if (blocked_set & (1U << SIGPIPE))
|
||||
/* The signal is blocked in the current thread. */
|
||||
pending_array[SIGPIPE] = 1;
|
||||
else
|
||||
{
|
||||
|
||||
@@ -9,7 +9,6 @@ Depends-on:
|
||||
signal-h
|
||||
threadlib
|
||||
sigprocmask [test $HAVE_PTHREAD_SIGMASK = 0 || test $REPLACE_PTHREAD_SIGMASK = 1]
|
||||
lock [test $HAVE_PTHREAD_SIGMASK = 0 || test $REPLACE_PTHREAD_SIGMASK = 1]
|
||||
memeq [test $HAVE_PTHREAD_SIGMASK = 0 || test $REPLACE_PTHREAD_SIGMASK = 1]
|
||||
|
||||
configure.ac:
|
||||
|
||||
@@ -10,6 +10,7 @@ signal-h
|
||||
stdint-h [test $HAVE_POSIX_SIGNALBLOCKING = 0]
|
||||
raise [test $HAVE_POSIX_SIGNALBLOCKING = 0]
|
||||
msvc-inval [test $HAVE_POSIX_SIGNALBLOCKING = 0]
|
||||
windows-spin [test $HAVE_POSIX_SIGNALBLOCKING = 0]
|
||||
|
||||
configure.ac:
|
||||
gl_SIGNALBLOCKING
|
||||
|
||||
Reference in New Issue
Block a user