This patch series adds a module 'asyncsafe-spin', with async-safe spin locks.

What is it?
-----------

An async-safe spin lock is a spin lock that can be used to communicate between
a thread that executes normal code and a thread that executes a signal handler.

The table in section 2.4.3 of
<https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html>
lists the functions that may be called from signal handlers. Unfortunately,
POSIX spin lock functions are not among them. Therefore, a new facility is
needed.

Where is it needed?
-------------------

It is needed anytime where you want to do locking between a signal handler
and normal code.

My use-case is to make the 'clean-temp' module multithread-safe. This module
registers file names (to delete) and file descriptors (to close). The latter
is necessary because POSIX allows for unlink() to fail with error EBUSY when
attempting to delete a file that has an open file descriptor. Now, when both
a normal thread tries to do
  close (fd);
and a signal handler also does
  close (fd);
there is a race condition:
  1. Thread A (normal code) starts to do
     close (fd);
     and is interrupted right when returning from the system call.
  2. An unrelated thread calls open(), and receives a file descriptor
     that is == fd. This is possible, since the kernel will reuse closed
     file descriptors.
  3. Thread A (or another thread B) gets to executes a signal handler
     that also does
       close (fd);
     But this now closes the wrong, unrelated file descriptor!

In more abstract terms: In multithreaded applications, it is forbidden to
close the same fd twice, because you never know what unrelated open() calls
are being executed in other threads. So, the 'close (fd)' must be guarded
by a once-only guard, i.e. effectively a lock.

How is it implemented?
----------------------

Fortunately, GCC has the necessary atomic built-ins for a long time already,
and there are few compilers not based on GCC:
  - AIX xlc and xlclang: are handled through a specific AIX _check_lock()
    function.
  - Solaris cc: are handled through inline asm, based on the code that
    GCC would generate.


2020-07-01  Bruno Haible  <br...@clisp.org>

        asyncsafe-spin: Add tests.
        * tests/test-asyncsafe-spin1.c: New file.
        * tests/test-asyncsafe-spin2.c: New file, based on tests/test-lock.c and
        tests/test-pthread-spin.c.
        * modules/asyncsafe-spin-tests: New file.

2020-07-01  Bruno Haible  <br...@clisp.org>

        asyncsafe-spin: New module.
        * lib/asyncsafe-spin.h: New file.
        * lib/asyncsafe-spin.c: New file, based on lib/pthread-spin.c.
        * modules/asyncsafe-spin: New file.


>From 79db2daddd96952239a77b74ebea9d40ccd5878b Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 1 Jul 2020 22:59:08 +0200
Subject: [PATCH 1/2] asyncsafe-spin: New module.

* lib/asyncsafe-spin.h: New file.
* lib/asyncsafe-spin.c: New file, based on lib/pthread-spin.c.
* modules/asyncsafe-spin: New file.
---
 ChangeLog              |   7 +
 lib/asyncsafe-spin.c   | 382 +++++++++++++++++++++++++++++++++++++++++++++++++
 lib/asyncsafe-spin.h   |  69 +++++++++
 modules/asyncsafe-spin |  27 ++++
 4 files changed, 485 insertions(+)
 create mode 100644 lib/asyncsafe-spin.c
 create mode 100644 lib/asyncsafe-spin.h
 create mode 100644 modules/asyncsafe-spin

diff --git a/ChangeLog b/ChangeLog
index 0051d82..179725a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2020-07-01  Bruno Haible  <br...@clisp.org>
 
+	asyncsafe-spin: New module.
+	* lib/asyncsafe-spin.h: New file.
+	* lib/asyncsafe-spin.c: New file, based on lib/pthread-spin.c.
+	* modules/asyncsafe-spin: New file.
+
+2020-07-01  Bruno Haible  <br...@clisp.org>
+
 	windows-spin: Fix race condition on multiprocessor systems.
 	* lib/windows-spin.c (glwthread_spin_init): Add a memory barrier.
 
diff --git a/lib/asyncsafe-spin.c b/lib/asyncsafe-spin.c
new file mode 100644
index 0000000..2198bcb
--- /dev/null
+++ b/lib/asyncsafe-spin.c
@@ -0,0 +1,382 @@
+/* Spin locks for communication between threads and signal handlers.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2020.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "asyncsafe-spin.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#if defined _AIX
+# include <sys/atomic_op.h>
+#endif
+
+#if defined _WIN32 && ! defined __CYGWIN__
+/* Use Windows threads.  */
+
+void
+asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
+{
+  glwthread_spin_init (lock);
+}
+
+void
+asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+                     const sigset_t *mask, sigset_t *saved_mask)
+{
+  sigprocmask (SIG_BLOCK, mask, saved_mask);
+  glwthread_spin_lock (lock);
+}
+
+void
+asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
+{
+  if (glwthread_spin_unlock (lock))
+    abort ();
+  sigprocmask (SIG_SETMASK, saved_mask, NULL);
+}
+
+void
+asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
+{
+  glwthread_spin_destroy (lock);
+}
+
+#elif HAVE_PTHREAD_H
+/* Use POSIX threads.  */
+
+/* We don't use semaphores (although sem_post() is allowed in signal handlers),
+   because it would require to link with -lrt on HP-UX 11, OSF/1, Solaris 10,
+   and also because on macOS only named semaphores work.
+
+   We don't use the C11 <stdatomic.h> (available in GCC >= 4.9) because it would
+   require to link with -latomic.  */
+
+# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) && !defined __ibmxl__
+/* Use GCC built-ins (available in GCC >= 4.7) that operate on the first byte of
+   the lock.
+   Documentation:
+   <https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/_005f_005fatomic-Builtins.html>
+ */
+
+#  if 1
+/* An implementation that verifies the unlocks.  */
+
+void
+asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
+{
+  __atomic_store_n (lock, 0, __ATOMIC_SEQ_CST);
+}
+
+void
+asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+                     const sigset_t *mask, sigset_t *saved_mask)
+{
+  sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
+
+  /* Wait until *lock becomes 0, then replace it with 1.  */
+  asyncsafe_spinlock_t zero;
+  while (!(zero = 0,
+           __atomic_compare_exchange_n (lock, &zero, 1, false,
+                                        __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)))
+    ;
+}
+
+void
+asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
+{
+  /* If *lock is 1, then replace it with 0.  */
+  asyncsafe_spinlock_t one = 1;
+  if (!__atomic_compare_exchange_n (lock, &one, 0, false,
+                                    __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
+    abort ();
+
+  sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
+}
+
+#  else
+/* An implementation that is a little bit more optimized, but does not verify
+   the unlocks.  */
+
+void
+asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
+{
+  __atomic_clear (lock, __ATOMIC_SEQ_CST);
+}
+
+void
+asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+                     const sigset_t *mask, sigset_t *saved_mask)
+{
+  sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
+
+  while (__atomic_test_and_set (lock, __ATOMIC_SEQ_CST))
+    ;
+}
+
+void
+asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
+{
+  __atomic_clear (lock, __ATOMIC_SEQ_CST);
+
+  sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
+}
+
+#  endif
+
+void
+asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
+{
+}
+
+# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) && !defined __ibmxl__
+/* Use GCC built-ins (available in GCC >= 4.1).
+   Documentation:
+   <https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html>  */
+
+void
+asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
+{
+  volatile unsigned int *vp = lock;
+  *vp = 0;
+  __sync_synchronize ();
+}
+
+void
+asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+                     const sigset_t *mask, sigset_t *saved_mask)
+{
+  sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
+
+  /* Wait until *lock becomes 0, then replace it with 1.  */
+  while (__sync_val_compare_and_swap (lock, 0, 1) != 0)
+    ;
+}
+
+void
+asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
+{
+  /* If *lock is 1, then replace it with 0.  */
+  if (__sync_val_compare_and_swap (lock, 1, 0) != 1)
+    abort ();
+
+  sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
+}
+
+void
+asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
+{
+}
+
+# elif defined _AIX
+/* AIX */
+
+void
+asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
+{
+  atomic_p vp = (int *) lock;
+  _clear_lock (vp, 0);
+}
+
+void
+asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+                     const sigset_t *mask, sigset_t *saved_mask)
+{
+  sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
+
+  atomic_p vp = (int *) lock;
+  while (_check_lock (vp, 0, 1))
+    ;
+}
+
+void
+asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
+{
+  atomic_p vp = (int *) lock;
+  if (_check_lock (vp, 1, 0))
+    abort ();
+
+  sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
+}
+
+void
+asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
+{
+}
+
+# elif (defined __GNUC__ || defined __SUNPRO_C) && (defined __sparc || defined __i386 || defined __x86_64__)
+/* For older versions of GCC, use inline assembly.
+   GCC and the Oracle Studio C compiler understand GCC's extended asm syntax,
+   but the plain Solaris cc understands only simple asm.  */
+/* An implementation that verifies the unlocks.  */
+
+static void
+memory_barrier (void)
+{
+#  if defined __GNUC__
+#   if defined __i386 || defined __x86_64__
+  asm volatile ("mfence");
+#   endif
+#   if defined __sparc
+  asm volatile ("membar 2");
+#   endif
+#  else
+#   if defined __i386 || defined __x86_64__
+  asm ("mfence");
+#   endif
+#   if defined __sparc
+  asm ("membar 2");
+#   endif
+#  endif
+}
+
+/* Store NEWVAL in *VP if the old value *VP is == CMP.
+   Return the old value.  */
+static unsigned int
+atomic_compare_and_swap (volatile unsigned int *vp, unsigned int cmp,
+                         unsigned int newval)
+{
+#  if defined __GNUC__
+  unsigned int oldval;
+#   if defined __i386 || defined __x86_64__
+  asm volatile (" lock\n cmpxchgl %3,(%1)"
+                : "=a" (oldval) : "r" (vp), "a" (cmp), "r" (newval) : "memory");
+#   endif
+#   if defined __sparc
+  asm volatile (" cas [%1],%2,%3\n"
+                " mov %3,%0"
+                : "=r" (oldval) : "r" (vp), "r" (cmp), "r" (newval) : "memory");
+#   endif
+  return oldval;
+#  else /* __SUNPRO_C */
+#   if defined __x86_64__
+  asm (" movl %esi,%eax\n"
+       " lock\n cmpxchgl %edx,(%rdi)");
+#   elif defined __i386
+  asm (" movl 16(%ebp),%ecx\n"
+       " movl 12(%ebp),%eax\n"
+       " movl 8(%ebp),%edx\n"
+       " lock\n cmpxchgl %ecx,(%edx)");
+#   endif
+#   if defined __sparc
+  asm (" cas [%i0],%i1,%i2\n"
+       " mov %i2,%i0");
+#   endif
+#  endif
+}
+
+void
+asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
+{
+  volatile unsigned int *vp = lock;
+  *vp = 0;
+  memory_barrier ();
+}
+
+void
+asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+                     const sigset_t *mask, sigset_t *saved_mask)
+{
+  sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
+
+  volatile unsigned int *vp = lock;
+  while (atomic_compare_and_swap (vp, 0, 1) != 0)
+    ;
+}
+
+void
+asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
+{
+  volatile unsigned int *vp = lock;
+  if (atomic_compare_and_swap (vp, 1, 0) != 1)
+    abort ();
+
+  sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
+}
+
+void
+asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
+{
+}
+
+# else
+/* Fallback code.  It has some race conditions.  */
+
+void
+asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
+{
+  volatile unsigned int *vp = lock;
+  *vp = 0;
+}
+
+void
+asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+                     const sigset_t *mask, sigset_t *saved_mask)
+{
+  sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
+
+  volatile unsigned int *vp = lock;
+  while (*vp)
+    ;
+  *vp = 1;
+}
+
+void
+asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
+{
+  volatile unsigned int *vp = lock;
+  *vp = 0;
+
+  sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
+}
+
+void
+asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
+{
+}
+
+# endif
+
+#else
+/* Provide a dummy implementation for single-threaded applications.  */
+
+void
+asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
+{
+}
+
+void
+asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+                     const sigset_t *mask, sigset_t *saved_mask)
+{
+  sigprocmask (SIG_BLOCK, mask, saved_mask);
+}
+
+void
+asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
+{
+  sigprocmask (SIG_SETMASK, saved_mask, NULL);
+}
+
+void
+asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
+{
+}
+
+#endif
diff --git a/lib/asyncsafe-spin.h b/lib/asyncsafe-spin.h
new file mode 100644
index 0000000..7e3693f
--- /dev/null
+++ b/lib/asyncsafe-spin.h
@@ -0,0 +1,69 @@
+/* Spin locks for communication between threads and signal handlers.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2020.  */
+
+#ifndef _ASYNCSAFE_SPIN_H
+#define _ASYNCSAFE_SPIN_H
+
+/* Usual spin locks are not allowed for communication between threads and signal
+   handlers, because the pthread_spin_* functions are not async-safe; see
+   <https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html>
+   section 2.4.3 Signal Actions.
+
+   This module provides spin locks with a similar API.  It can be used like this,
+   both in regular multithreaded code and in signal handlers:
+
+       sigset_t saved_mask;
+       asyncsafe_spin_lock (&lock, &mask, &saved_mask);
+       do_something_contentious ();
+       asyncsafe_spin_unlock (&lock, &saved_mask);
+
+   The mask you specify here is the set of signals whose handlers might want to
+   take the same lock.
+
+   asyncsafe_spin_lock/unlock use pthread_sigmask, to ensure that while a thread
+   is executing such code, no signal handler will start such code for the same
+   lock *in the same thread* (because if this happened, the signal handler would
+   hang!).  */
+
+#include <signal.h>
+
+#if defined _WIN32 && ! defined __CYGWIN__
+# include "windows-spin.h"
+typedef glwthread_spinlock_t asyncsafe_spinlock_t;
+# define ASYNCSAFE_SPIN_INIT GLWTHREAD_SPIN_INIT
+#else
+typedef unsigned int asyncsafe_spinlock_t;
+# define ASYNCSAFE_SPIN_INIT 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void asyncsafe_spin_init (asyncsafe_spinlock_t *lock);
+extern void asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+                                 const sigset_t *mask, sigset_t *saved_mask);
+extern void asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock,
+                                   const sigset_t *saved_mask);
+extern void asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ASYNCSAFE_SPIN_H */
diff --git a/modules/asyncsafe-spin b/modules/asyncsafe-spin
new file mode 100644
index 0000000..40d181b
--- /dev/null
+++ b/modules/asyncsafe-spin
@@ -0,0 +1,27 @@
+Description:
+Spin locks for communication between threads and signal handlers.
+
+Files:
+lib/asyncsafe-spin.h
+lib/asyncsafe-spin.c
+
+Depends-on:
+signal-h
+stdbool
+sigprocmask
+windows-spin
+
+configure.ac:
+AC_CHECK_HEADERS_ONCE([pthread.h])
+
+Makefile.am:
+lib_SOURCES += asyncsafe-spin.c
+
+Include:
+"asyncsafe-spin.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.7.4

>From a0b286c0a5d50f2b52692f61f09e4500bcd98ebe Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 1 Jul 2020 23:00:44 +0200
Subject: [PATCH 2/2] asyncsafe-spin: Add tests.

* tests/test-asyncsafe-spin1.c: New file.
* tests/test-asyncsafe-spin2.c: New file, based on tests/test-lock.c and
tests/test-pthread-spin.c.
* modules/asyncsafe-spin-tests: New file.
---
 ChangeLog                    |   8 ++
 modules/asyncsafe-spin-tests |  20 ++++
 tests/test-asyncsafe-spin1.c |  61 +++++++++++
 tests/test-asyncsafe-spin2.c | 245 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 334 insertions(+)
 create mode 100644 modules/asyncsafe-spin-tests
 create mode 100644 tests/test-asyncsafe-spin1.c
 create mode 100644 tests/test-asyncsafe-spin2.c

diff --git a/ChangeLog b/ChangeLog
index 179725a..3cde294 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2020-07-01  Bruno Haible  <br...@clisp.org>
 
+	asyncsafe-spin: Add tests.
+	* tests/test-asyncsafe-spin1.c: New file.
+	* tests/test-asyncsafe-spin2.c: New file, based on tests/test-lock.c and
+	tests/test-pthread-spin.c.
+	* modules/asyncsafe-spin-tests: New file.
+
+2020-07-01  Bruno Haible  <br...@clisp.org>
+
 	asyncsafe-spin: New module.
 	* lib/asyncsafe-spin.h: New file.
 	* lib/asyncsafe-spin.c: New file, based on lib/pthread-spin.c.
diff --git a/modules/asyncsafe-spin-tests b/modules/asyncsafe-spin-tests
new file mode 100644
index 0000000..301c2af
--- /dev/null
+++ b/modules/asyncsafe-spin-tests
@@ -0,0 +1,20 @@
+Files:
+tests/test-asyncsafe-spin1.c
+tests/test-asyncsafe-spin2.c
+tests/atomic-int-gnulib.h
+m4/semaphore.m4
+
+Depends-on:
+thread
+lock
+yield
+
+configure.ac:
+AC_CHECK_HEADERS_ONCE([semaphore.h])
+AC_CHECK_DECLS_ONCE([alarm])
+AC_REQUIRE([gl_SEMAPHORE])
+
+Makefile.am:
+TESTS += test-asyncsafe-spin1 test-asyncsafe-spin2
+check_PROGRAMS += test-asyncsafe-spin1 test-asyncsafe-spin2
+test_asyncsafe_spin2_LDADD = $(LDADD) @LIBMULTITHREAD@ @YIELD_LIB@ @LIB_SEMAPHORE@
diff --git a/tests/test-asyncsafe-spin1.c b/tests/test-asyncsafe-spin1.c
new file mode 100644
index 0000000..e345e17
--- /dev/null
+++ b/tests/test-asyncsafe-spin1.c
@@ -0,0 +1,61 @@
+/* Test of spin locks for communication between threads and signal handlers.
+   Copyright (C) 2005, 2008-2020 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2020.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "asyncsafe-spin.h"
+
+#include <signal.h>
+
+asyncsafe_spinlock_t global_spin_lock = ASYNCSAFE_SPIN_INIT;
+
+int
+main (void)
+{
+  sigset_t set;
+
+  sigemptyset (&set);
+  sigaddset (&set, SIGINT);
+
+  /* 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);
+  }
+
+  /* Check a spin-lock initialized through asyncsafe_spin_init.  */
+  {
+    asyncsafe_spinlock_t local_spin_lock;
+    int i;
+
+    asyncsafe_spin_init (&local_spin_lock);
+
+    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_destroy (&local_spin_lock);
+  }
+
+  return 0;
+}
diff --git a/tests/test-asyncsafe-spin2.c b/tests/test-asyncsafe-spin2.c
new file mode 100644
index 0000000..cb0364f
--- /dev/null
+++ b/tests/test-asyncsafe-spin2.c
@@ -0,0 +1,245 @@
+/* Test of spin locks for communication between threads and signal handlers.
+   Copyright (C) 2005, 2008-2020 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2005.  */
+
+#include <config.h>
+
+#if USE_ISOC_THREADS || USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS || USE_WINDOWS_THREADS
+
+/* Whether to enable locking.
+   Uncomment this to get a test program without locking, to verify that
+   it crashes.  */
+#define ENABLE_LOCKING 1
+
+/* Whether to help the scheduler through explicit yield().
+   Uncomment this to see if the operating system has a fair scheduler.  */
+#define EXPLICIT_YIELD 1
+
+/* Whether to print debugging messages.  */
+#define ENABLE_DEBUGGING 0
+
+/* Number of simultaneous threads.  */
+#define THREAD_COUNT 10
+
+/* Number of operations performed in each thread.  */
+#if !(defined _WIN32 && ! defined __CYGWIN__) && HAVE_PTHREAD_H && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) && !defined __ibmxl__
+/* The GCC built-ins are known to work fine.  */
+# define REPEAT_COUNT 5000
+#else
+/* This is quite high, because with a smaller count, say 50000, we often get
+   an "OK" result even with the racy implementation that we pick on Fedora 13
+   Linux/x86_64 (gcc 4.4).  */
+# define REPEAT_COUNT 100000
+#endif
+
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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_destroy(lock) (void)(lock)
+#endif
+
+#include "glthread/lock.h"
+#include "glthread/thread.h"
+#include "glthread/yield.h"
+
+#if HAVE_DECL_ALARM
+# include <signal.h>
+# include <unistd.h>
+#endif
+
+#include "atomic-int-gnulib.h"
+
+#if ENABLE_DEBUGGING
+# define dbgprintf printf
+#else
+# define dbgprintf if (0) printf
+#endif
+
+#if EXPLICIT_YIELD
+# define yield() gl_thread_yield ()
+#else
+# define yield()
+#endif
+
+static sigset_t signals_to_block;
+
+#define ACCOUNT_COUNT 4
+
+static int account[ACCOUNT_COUNT];
+
+static int
+random_account (void)
+{
+  return ((unsigned int) rand () >> 3) % ACCOUNT_COUNT;
+}
+
+static void
+check_accounts (void)
+{
+  int i, sum;
+
+  sum = 0;
+  for (i = 0; i < ACCOUNT_COUNT; i++)
+    sum += account[i];
+  if (sum != ACCOUNT_COUNT * 1000)
+    abort ();
+}
+
+
+/* ------------------- Test use like normal locks ------------------- */
+
+/* Test normal locks by having several bank accounts and several threads
+   which shuffle around money between the accounts and another thread
+   checking that all the money is still there.  */
+
+static asyncsafe_spinlock_t my_lock;
+
+static void *
+lock_mutator_thread (void *arg)
+{
+  int repeat;
+
+  for (repeat = REPEAT_COUNT; repeat > 0; repeat--)
+    {
+      sigset_t saved_signals;
+      int i1, i2, value;
+
+      dbgprintf ("Mutator %p before lock\n", gl_thread_self_pointer ());
+      asyncsafe_spin_lock (&my_lock, &signals_to_block, &saved_signals);
+      dbgprintf ("Mutator %p after  lock\n", gl_thread_self_pointer ());
+
+      i1 = random_account ();
+      i2 = random_account ();
+      value = ((unsigned int) rand () >> 3) % 10;
+      account[i1] += value;
+      account[i2] -= value;
+
+      dbgprintf ("Mutator %p before unlock\n", gl_thread_self_pointer ());
+      asyncsafe_spin_unlock (&my_lock, &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);
+      check_accounts ();
+      asyncsafe_spin_unlock (&my_lock, &saved_signals);
+      dbgprintf ("Mutator %p after  check unlock\n", gl_thread_self_pointer ());
+
+      yield ();
+    }
+
+  dbgprintf ("Mutator %p dying.\n", gl_thread_self_pointer ());
+  return NULL;
+}
+
+static struct atomic_int lock_checker_done;
+
+static void *
+lock_checker_thread (void *arg)
+{
+  while (get_atomic_int_value (&lock_checker_done) == 0)
+    {
+      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);
+      check_accounts ();
+      asyncsafe_spin_unlock (&my_lock, &saved_signals);
+      dbgprintf ("Checker %p after  check unlock\n", gl_thread_self_pointer ());
+
+      yield ();
+    }
+
+  dbgprintf ("Checker %p dying.\n", gl_thread_self_pointer ());
+  return NULL;
+}
+
+static void
+test_asyncsafe_spin (void)
+{
+  int i;
+  gl_thread_t checkerthread;
+  gl_thread_t threads[THREAD_COUNT];
+
+  /* Initialization.  */
+  for (i = 0; i < ACCOUNT_COUNT; i++)
+    account[i] = 1000;
+  init_atomic_int (&lock_checker_done);
+  set_atomic_int_value (&lock_checker_done, 0);
+
+  /* Spawn the threads.  */
+  checkerthread = gl_thread_create (lock_checker_thread, NULL);
+  for (i = 0; i < THREAD_COUNT; i++)
+    threads[i] = gl_thread_create (lock_mutator_thread, NULL);
+
+  /* Wait for the threads to terminate.  */
+  for (i = 0; i < THREAD_COUNT; i++)
+    gl_thread_join (threads[i], NULL);
+  set_atomic_int_value (&lock_checker_done, 1);
+  gl_thread_join (checkerthread, NULL);
+  check_accounts ();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+int
+main ()
+{
+#if HAVE_DECL_ALARM
+  /* Declare failure if test takes too long, by using default abort
+     caused by SIGALRM.  */
+  int alarm_value = 600;
+  signal (SIGALRM, SIG_DFL);
+  alarm (alarm_value);
+#endif
+
+  sigemptyset (&signals_to_block);
+  sigaddset (&signals_to_block, SIGINT);
+
+  asyncsafe_spin_init (&my_lock);
+
+  printf ("Starting test_asyncsafe_spin ..."); fflush (stdout);
+  test_asyncsafe_spin ();
+  printf (" OK\n"); fflush (stdout);
+
+  return 0;
+}
+
+#else
+
+/* No multithreading available.  */
+
+#include <stdio.h>
+
+int
+main ()
+{
+  fputs ("Skipping test: multithreading not enabled\n", stderr);
+  return 77;
+}
+
+#endif
-- 
2.7.4

Reply via email to