+2025-05-16 Bruno Haible <bruno@clisp.org>
+
+ asyncsafe-spin: Fix race condition on native Windows.
+ * lib/asyncsafe-spin.h (asyncsafe_spin_lock, asyncsafe_spin_unlock): Add
+ from_signal_handler parameter.
+ * lib/asyncsafe-spin.c (asyncsafe_spin_lock, asyncsafe_spin_unlock):
+ Likewise.
+ * modules/asyncsafe-spin (Depends-on): Add bool.
+ * tests/test-asyncsafe-spin1.c (main): Update.
+ * tests/test-asyncsafe-spin2.c (lock_mutator_thread,
+ lock_checker_thread): Update.
+ * lib/clean-temp-simple.c (clean_temp_asyncsafe_close): Update.
+ * lib/clean-temp.c (asyncsafe_fclose_variant): Update.
+
2025-05-16 Bruno Haible <bruno@clisp.org>
sigprocmask: Make multithread-safe on native Windows.
void
asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+ bool from_signal_handler,
const sigset_t *mask, sigset_t *saved_mask)
{
- sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
+ /* On all platforms, when not running in a signal handler, we need to block
+ the signals until the corresponding asyncsafe_spin_unlock() invocation.
+ This is needed because if, during that period, a signal occurred and it
+ happened to run in the current thread and it were to wait on this spin
+ lock, it would hang.
+ On platforms other than native Windows, it is useful to do the same
+ thing also within a signal handler, since signals may remain enabled
+ while a signal handler runs. It is possible to do this because
+ sigprocmask() is safe to call from within a signal handler, see
+ POSIX section "Signal Actions"
+ <https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03>.
+ (In other words, sigprocmask() is atomic, because it is implemented as a
+ system call.)
+ Whereas on native Windows, sigprocmask() is not atomic, because it
+ manipulates global variables. Therefore in this case, we are *not*
+ allowed to call it from within a signal handler. */
+#if defined _WIN32 && !defined __CYGWIN__
+ if (!from_signal_handler)
+#endif
+ sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
+
glthread_spinlock_lock (lock);
}
void
-asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
+asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock,
+ bool from_signal_handler,
+ const sigset_t *saved_mask)
{
if (glthread_spinlock_unlock (lock))
abort ();
- sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
+
+#if defined _WIN32 && !defined __CYGWIN__
+ if (!from_signal_handler)
+#endif
+ sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
}
void
both in regular multithreaded code and in signal handlers:
sigset_t saved_mask;
- asyncsafe_spin_lock (&lock, &mask, &saved_mask);
+ asyncsafe_spin_lock (&lock, from_signal_handler, &mask, &saved_mask);
do_something_contentious ();
- asyncsafe_spin_unlock (&lock, &saved_mask);
+ asyncsafe_spin_unlock (&lock, from_signal_handler, &saved_mask);
The mask you specify here is the set of signals whose handlers might want to
take the same lock.
extern void asyncsafe_spin_init (asyncsafe_spinlock_t *lock);
extern void asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+ bool from_signal_handler,
const sigset_t *mask, sigset_t *saved_mask);
extern void asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock,
+ bool from_signal_handler,
const sigset_t *saved_mask);
extern void asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock);
int ret;
int saved_errno;
- asyncsafe_spin_lock (&element->lock, fatal_signal_set, &saved_mask);
+ asyncsafe_spin_lock (&element->lock, true, fatal_signal_set, &saved_mask);
if (!element->closed)
{
ret = close (element->fd);
ret = 0;
saved_errno = 0;
}
- asyncsafe_spin_unlock (&element->lock, &saved_mask);
+ asyncsafe_spin_unlock (&element->lock, true, &saved_mask);
element->done = true;
errno = saved_errno;
int ret;
int saved_errno;
- asyncsafe_spin_lock (&element->lock, get_fatal_signal_set (), &saved_mask);
+ asyncsafe_spin_lock (&element->lock, false,
+ get_fatal_signal_set (), &saved_mask);
if (!element->closed)
{
ret = fclose_variant (fp); /* invokes close (element->fd) */
ret = 0;
saved_errno = 0;
}
- asyncsafe_spin_unlock (&element->lock, &saved_mask);
+ asyncsafe_spin_unlock (&element->lock, false,
+ &saved_mask);
element->done = true;
errno = saved_errno;
Depends-on:
signal-h
+bool
sigprocmask
spin
/* Check a spin-lock initialized through the constant initializer. */
{
sigset_t saved_set;
- asyncsafe_spin_lock (&global_spin_lock, &set, &saved_set);
- asyncsafe_spin_unlock (&global_spin_lock, &saved_set);
+ asyncsafe_spin_lock (&global_spin_lock, false, &set, &saved_set);
+ asyncsafe_spin_unlock (&global_spin_lock, false, &saved_set);
}
/* Check a spin-lock initialized through asyncsafe_spin_init. */
for (i = 0; i < 10; i++)
{
sigset_t saved_set;
- asyncsafe_spin_lock (&local_spin_lock, &set, &saved_set);
- asyncsafe_spin_unlock (&local_spin_lock, &saved_set);
+ asyncsafe_spin_lock (&local_spin_lock, false, &set, &saved_set);
+ asyncsafe_spin_unlock (&local_spin_lock, false, &saved_set);
}
asyncsafe_spin_destroy (&local_spin_lock);
#include "asyncsafe-spin.h"
#if !ENABLE_LOCKING
# define asyncsafe_spin_init(lock) (void)(lock)
-# define asyncsafe_spin_lock(lock, mask, saved_mask) \
- ((void)(lock), (void)(mask), (void)(saved_mask))
-# define asyncsafe_spin_unlock(lock, saved_mask) \
- ((void)(lock), (void)(saved_mask))
+# define asyncsafe_spin_lock(lock, from, mask, saved_mask) \
+ ((void)(lock), (void)(from), (void)(mask), (void)(saved_mask))
+# define asyncsafe_spin_unlock(lock, from, saved_mask) \
+ ((void)(lock), (void)(from), (void)(saved_mask))
# define asyncsafe_spin_destroy(lock) (void)(lock)
#endif
int i1, i2, value;
dbgprintf ("Mutator %p before lock\n", gl_thread_self_pointer ());
- asyncsafe_spin_lock (&my_lock, &signals_to_block, &saved_signals);
+ asyncsafe_spin_lock (&my_lock, false, &signals_to_block, &saved_signals);
dbgprintf ("Mutator %p after lock\n", gl_thread_self_pointer ());
i1 = random_account ();
account[i2] -= value;
dbgprintf ("Mutator %p before unlock\n", gl_thread_self_pointer ());
- asyncsafe_spin_unlock (&my_lock, &saved_signals);
+ asyncsafe_spin_unlock (&my_lock, false, &saved_signals);
dbgprintf ("Mutator %p after unlock\n", gl_thread_self_pointer ());
dbgprintf ("Mutator %p before check lock\n", gl_thread_self_pointer ());
- asyncsafe_spin_lock (&my_lock, &signals_to_block, &saved_signals);
+ asyncsafe_spin_lock (&my_lock, false, &signals_to_block, &saved_signals);
check_accounts ();
- asyncsafe_spin_unlock (&my_lock, &saved_signals);
+ asyncsafe_spin_unlock (&my_lock, false, &saved_signals);
dbgprintf ("Mutator %p after check unlock\n", gl_thread_self_pointer ());
yield ();
sigset_t saved_signals;
dbgprintf ("Checker %p before check lock\n", gl_thread_self_pointer ());
- asyncsafe_spin_lock (&my_lock, &signals_to_block, &saved_signals);
+ asyncsafe_spin_lock (&my_lock, false, &signals_to_block, &saved_signals);
check_accounts ();
- asyncsafe_spin_unlock (&my_lock, &saved_signals);
+ asyncsafe_spin_unlock (&my_lock, false, &saved_signals);
dbgprintf ("Checker %p after check unlock\n", gl_thread_self_pointer ());
yield ();