The code of module 'asyncsafe-spin' consists of a multithread-safe spin lock implementation (that works fine even on native Windows), with an add-on intented to make it async signal safe (this part does not work reliably on native Windows).
This suggests that it makes sense to rebase 'asyncsafe-spin' on top of a module 'spin', that implements purely the multithread-safe spin locks. 2025-05-14 Bruno Haible <br...@clisp.org> asyncsafe-spin: Rely on module 'spin'. * lib/asyncsafe-spin.h: Include glthread/spin.h. (asyncsafe_spinlock_t, ASYNCSAFE_SPIN_INIT): Simplify by using gl_spinlock_t, gl_spinlock_initializer. * lib/asyncsafe-spin.c: Simplify by using glthread_spin_* functions. * modules/asyncsafe-spin (Files): Remove m4/atomic-cas.m4. (Depends-on): Add spin. Remove bool, windows-spin, sparcv8+. (configure.ac): Remove tests. 2025-05-14 Bruno Haible <br...@clisp.org> spin: Add tests. * tests/test-spin1.c: New file, based on tests/test-asyncsafe-spin1.c. * tests/test-spin2.c: New file, based on tests/test-asyncsafe-spin2.c. * modules/spin-tests: New file. spin: New module. * lib/glthread/spin.h: New file, based on lib/glthread/lock.h and lib/asyncsafe-spin.h. * lib/glthread/spin.c: New file, based on lib/asyncsafe-spin.c. * modules/spin: New file. * doc/multithread.texi (Choosing a multithreading API): Spin locks no longer require POSIX threads. (Gnulib multithreading): Mention the 'spin' module.
>From a258598ec7d20ad6a853ee2c5e811a35b506a1f2 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Wed, 14 May 2025 12:51:46 +0200 Subject: [PATCH 1/3] spin: New module. * lib/glthread/spin.h: New file, based on lib/glthread/lock.h and lib/asyncsafe-spin.h. * lib/glthread/spin.c: New file, based on lib/asyncsafe-spin.c. * modules/spin: New file. * doc/multithread.texi (Choosing a multithreading API): Spin locks no longer require POSIX threads. (Gnulib multithreading): Mention the 'spin' module. --- ChangeLog | 11 ++ doc/multithread.texi | 6 +- lib/glthread/spin.c | 298 +++++++++++++++++++++++++++++++++++++++++++ lib/glthread/spin.h | 94 ++++++++++++++ modules/bind-tests | 1 + modules/spin | 30 +++++ 6 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 lib/glthread/spin.c create mode 100644 lib/glthread/spin.h create mode 100644 modules/spin diff --git a/ChangeLog b/ChangeLog index 85028d3684..6c37f8b1a4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2025-05-14 Bruno Haible <br...@clisp.org> + + spin: New module. + * lib/glthread/spin.h: New file, based on lib/glthread/lock.h and + lib/asyncsafe-spin.h. + * lib/glthread/spin.c: New file, based on lib/asyncsafe-spin.c. + * modules/spin: New file. + * doc/multithread.texi (Choosing a multithreading API): Spin locks no + longer require POSIX threads. + (Gnulib multithreading): Mention the 'spin' module. + 2025-05-12 Bruno Haible <br...@clisp.org> file-has-acl: Fix compilation error on Solaris (regression 2025-05-09). diff --git a/doc/multithread.texi b/doc/multithread.texi index b24c35a207..1316404bef 100644 --- a/doc/multithread.texi +++ b/doc/multithread.texi @@ -107,7 +107,7 @@ Here are guidelines for determining which multithreading API is best for your code. -In programs that use advanced POSIX APIs, such as spin locks, +In programs that use advanced POSIX APIs, such as detached threads (@code{pthread_detach}), signal blocking (@code{pthread_sigmask}), priorities (@code{pthread_setschedparam}), @@ -204,6 +204,8 @@ @code{<glthread/tls.h>} @item @code{<glthread/yield.h>} +@item +@code{<glthread/spin.h>} @end itemize To make use of Gnulib multithreading, use the following Gnulib modules: @@ -213,6 +215,7 @@ @mindex cond @mindex tls @mindex yield +@mindex spin @multitable @columnfractions .85 .15 @headitem Purpose @tab Module @item For thread creation and management:@tie{} @tab @code{thread} @@ -222,6 +225,7 @@ @item For ``condition variables'' (wait queues):@tie{} @tab @code{cond} @item For thread-local storage:@tie{} @tab @code{tls} @item For relinquishing control:@tie{} @tab @code{yield} +@item For spin locks:@tie{} @tab @code{spin} @end multitable The Gnulib multithreading supports a configure option diff --git a/lib/glthread/spin.c b/lib/glthread/spin.c new file mode 100644 index 0000000000..c8dd483b64 --- /dev/null +++ b/lib/glthread/spin.c @@ -0,0 +1,298 @@ +/* Spin locks in multithreaded situations. + Copyright (C) 2020-2025 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser 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 "glthread/spin.h" + +#include <errno.h> +#if defined _AIX +# include <sys/atomic_op.h> +#endif +#if 0x590 <= __SUNPRO_C && __STDC__ +# define asm __asm +#endif + +#if defined _WIN32 && !defined __CYGWIN__ +/* Use Windows threads. */ + +/* All definitions are inline in glthread/spin.h. */ + +#else + +/* 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) \ + || __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 1)) \ + && !defined __ibmxl__ +/* Use GCC built-ins (available in GCC >= 4.7 and clang >= 3.1) 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 +glthread_spinlock_init (gl_spinlock_t *lock) +{ + __atomic_store_n (lock, 0, __ATOMIC_SEQ_CST); +} + +void +glthread_spinlock_lock (gl_spinlock_t *lock) +{ + /* Wait until *lock becomes 0, then replace it with 1. */ + gl_spinlock_t zero; + while (!(zero = 0, + __atomic_compare_exchange_n (lock, &zero, 1, false, + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))) + ; +} + +int +glthread_spinlock_unlock (gl_spinlock_t *lock) +{ + /* If *lock is 1, then replace it with 0. */ + gl_spinlock_t one = 1; + if (!__atomic_compare_exchange_n (lock, &one, 0, false, + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) + return EINVAL; + return 0; +} + +# else +/* An implementation that is a little bit more optimized, but does not verify + the unlocks. */ + +void +glthread_spinlock_init (gl_spinlock_t *lock) +{ + __atomic_clear (lock, __ATOMIC_SEQ_CST); +} + +void +glthread_spinlock_lock (gl_spinlock_t *lock) +{ + while (__atomic_test_and_set (lock, __ATOMIC_SEQ_CST)) + ; +} + +int +glthread_spinlock_unlock (gl_spinlock_t *lock) +{ + __atomic_clear (lock, __ATOMIC_SEQ_CST); + return 0; +} + +# endif + +# elif (((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) \ + || __clang_major__ >= 3) \ + && HAVE_ATOMIC_COMPARE_AND_SWAP_GCC41) +/* Use GCC built-ins (available on many platforms with GCC >= 4.1 or + clang >= 3.0). + Documentation: + <https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html> */ + +void +glthread_spinlock_init (gl_spinlock_t *lock) +{ + volatile unsigned int *vp = lock; + *vp = 0; + __sync_synchronize (); +} + +void +glthread_spinlock_lock (gl_spinlock_t *lock) +{ + /* Wait until *lock becomes 0, then replace it with 1. */ + while (__sync_val_compare_and_swap (lock, 0, 1) != 0) + ; +} + +int +glthread_spinlock_unlock (gl_spinlock_t *lock) +{ + /* If *lock is 1, then replace it with 0. */ + if (__sync_val_compare_and_swap (lock, 1, 0) != 1) + return EINVAL; + return 0; +} + +# elif defined _AIX +/* AIX */ + +void +glthread_spinlock_init (gl_spinlock_t *lock) +{ + atomic_p vp = (int *) lock; + _clear_lock (vp, 0); +} + +void +glthread_spinlock_lock (gl_spinlock_t *lock) +{ + atomic_p vp = (int *) lock; + while (_check_lock (vp, 0, 1)) + ; +} + +int +glthread_spinlock_unlock (gl_spinlock_t *lock) +{ + atomic_p vp = (int *) lock; + if (_check_lock (vp, 1, 0)) + return EINVAL; + return 0; +} + +# elif ((defined __GNUC__ || defined __clang__ || defined __SUNPRO_C) && (defined __sparc || defined __i386 || defined __x86_64__)) || (defined __TINYC__ && (defined __i386 || defined __x86_64__)) +/* For older versions of GCC or clang, use inline assembly. + GCC, clang, and the Oracle Studio C 12 compiler understand GCC's extended + asm syntax, but the plain Oracle Studio C 11 compiler understands only + simple asm. */ +/* An implementation that verifies the unlocks. */ + +static void +memory_barrier (void) +{ +# if defined __GNUC__ || defined __clang__ || __SUNPRO_C >= 0x590 || defined __TINYC__ +# if defined __i386 || defined __x86_64__ +# if defined __TINYC__ && defined __i386 + /* Cannot use the SSE instruction "mfence" with this compiler. */ + asm volatile ("lock orl $0,(%esp)"); +# else + asm volatile ("mfence"); +# endif +# 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__ || defined __clang__ || __SUNPRO_C >= 0x590 || defined __TINYC__ + 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 +glthread_spinlock_init (gl_spinlock_t *lock) +{ + volatile unsigned int *vp = lock; + *vp = 0; + memory_barrier (); +} + +void +glthread_spinlock_lock (gl_spinlock_t *lock) +{ + volatile unsigned int *vp = lock; + while (atomic_compare_and_swap (vp, 0, 1) != 0) + ; +} + +int +glthread_spinlock_unlock (gl_spinlock_t *lock) +{ + volatile unsigned int *vp = lock; + if (atomic_compare_and_swap (vp, 1, 0) != 1) + return EINVAL; + return 0; +} + +# else +/* Fallback code. It has some race conditions. */ + +void +glthread_spinlock_init (gl_spinlock_t *lock) +{ + volatile unsigned int *vp = lock; + *vp = 0; +} + +void +glthread_spinlock_lock (gl_spinlock_t *lock) +{ + volatile unsigned int *vp = lock; + while (*vp) + ; + *vp = 1; +} + +int +glthread_spinlock_unlock (gl_spinlock_t *lock) +{ + volatile unsigned int *vp = lock; + *vp = 0; + return 0; +} + +# endif + +void +glthread_spinlock_destroy (gl_spinlock_t *lock) +{ +} + +#endif diff --git a/lib/glthread/spin.h b/lib/glthread/spin.h new file mode 100644 index 0000000000..8a2405f940 --- /dev/null +++ b/lib/glthread/spin.h @@ -0,0 +1,94 @@ +/* Spin locks in multithreaded situations. + Copyright (C) 2005-2025 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2025. */ + +/* This file contains short-duration locking primitives for use with a given + thread library. + + Spin locks: + Type: gl_spinlock_t + Declaration: gl_spinlock_define(extern, name) + Initializer: gl_spinlock_define_initialized(, name) + Initialization: gl_spinlock_init (name); + Taking the lock: gl_spinlock_lock (name); + Releasing the lock: gl_spinlock_unlock (name); + De-initialization: gl_spinlock_destroy (name); + Equivalent functions with control of error handling: + Initialization: glthread_spinlock_init (&name); + Taking the lock: glthread_spinlock_lock (&name); + Releasing the lock: err = glthread_spinlock_unlock (&name); + De-initialization: glthread_spinlock_destroy (&name); +*/ + + +#ifndef _SPINLOCK_H +#define _SPINLOCK_H + +#if defined _WIN32 && !defined __CYGWIN__ +# include "windows-spin.h" +typedef glwthread_spinlock_t gl_spinlock_t; +# define gl_spinlock_initializer GLWTHREAD_SPIN_INIT +#else +typedef unsigned int gl_spinlock_t; +# define gl_spinlock_initializer 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdlib.h> + +#define gl_spinlock_define(STORAGECLASS, NAME) \ + STORAGECLASS gl_spinlock_t NAME; +#define gl_spinlock_define_initialized(STORAGECLASS, NAME) \ + STORAGECLASS gl_spinlock_t NAME = gl_spinlock_initializer; +#define gl_spinlock_init(NAME) \ + glthread_spinlock_init (&NAME) +#define gl_spinlock_lock(NAME) \ + glthread_spinlock_lock (&NAME) +#define gl_spinlock_unlock(NAME) \ + do \ + { \ + if (glthread_spinlock_unlock (&NAME)) \ + abort (); \ + } \ + while (0) +#define gl_spinlock_destroy(NAME) \ + glthread_spinlock_destroy (&NAME) + +#if defined _WIN32 && !defined __CYGWIN__ +# define glthread_spinlock_init(lock) \ + glwthread_spin_init (lock) +# define glthread_spinlock_lock(lock) \ + ((void) glwthread_spin_lock (lock)) +# define glthread_spinlock_unlock(lock) \ + glwthread_spin_unlock (lock) +# define glthread_spinlock_destroy(lock) \ + ((void) glwthread_spin_destroy (lock)) +#else +extern void glthread_spinlock_init (gl_spinlock_t *lock); +extern void glthread_spinlock_lock (gl_spinlock_t *lock); +extern int glthread_spinlock_unlock (gl_spinlock_t *lock); +extern void glthread_spinlock_destroy (gl_spinlock_t *lock); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _SPINLOCK_H */ diff --git a/modules/bind-tests b/modules/bind-tests index 3d2971e55c..f7b5a4782a 100644 --- a/modules/bind-tests +++ b/modules/bind-tests @@ -14,3 +14,4 @@ Makefile.am: TESTS += test-bind check_PROGRAMS += test-bind test_bind_LDADD = $(LDADD) @LIBSOCKET@ $(INET_PTON_LIB) +test_bind_LDFLAGS = $(LDFLAGS) @LIBSOCKET@ $(INET_PTON_LIB) diff --git a/modules/spin b/modules/spin new file mode 100644 index 0000000000..756c7b5e98 --- /dev/null +++ b/modules/spin @@ -0,0 +1,30 @@ +Description: +Spin locks in multithreaded situations. + +Files: +lib/glthread/spin.h +lib/glthread/spin.c +m4/atomic-cas.m4 + +Depends-on: +windows-spin +sparcv8+ + +configure.ac: +AC_REQUIRE([gl_ATOMIC_COMPARE_AND_SWAP]) +gl_CONDITIONAL([GL_COND_OBJ_SPIN], + [case "$host_os" in mingw* | windows*) false;; *) true;; esac]) + +Makefile.am: +if GL_COND_OBJ_SPIN +lib_SOURCES += glthread/spin.c +endif + +Include: +"glthread/spin.h" + +License: +LGPLv2+ + +Maintainer: +all -- 2.43.0
>From 1bf1bea69bb0c590f01d1e312cef8fd71c5dc5e4 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Wed, 14 May 2025 13:57:28 +0200 Subject: [PATCH 2/3] spin: Add tests. * tests/test-spin1.c: New file, based on tests/test-asyncsafe-spin1.c. * tests/test-spin2.c: New file, based on tests/test-asyncsafe-spin2.c. * modules/spin-tests: New file. --- ChangeLog | 5 + modules/spin-tests | 21 ++++ tests/test-spin1.c | 52 ++++++++++ tests/test-spin2.c | 240 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 318 insertions(+) create mode 100644 modules/spin-tests create mode 100644 tests/test-spin1.c create mode 100644 tests/test-spin2.c diff --git a/ChangeLog b/ChangeLog index 6c37f8b1a4..b903bacf5e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2025-05-14 Bruno Haible <br...@clisp.org> + spin: Add tests. + * tests/test-spin1.c: New file, based on tests/test-asyncsafe-spin1.c. + * tests/test-spin2.c: New file, based on tests/test-asyncsafe-spin2.c. + * modules/spin-tests: New file. + spin: New module. * lib/glthread/spin.h: New file, based on lib/glthread/lock.h and lib/asyncsafe-spin.h. diff --git a/modules/spin-tests b/modules/spin-tests new file mode 100644 index 0000000000..56a29ee10d --- /dev/null +++ b/modules/spin-tests @@ -0,0 +1,21 @@ +Files: +tests/test-spin1.c +tests/test-spin2.c +tests/atomic-int-gnulib.h +m4/semaphore.m4 + +Depends-on: +thread +lock +yield +random + +configure.ac: +AC_CHECK_HEADERS_ONCE([semaphore.h]) +AC_CHECK_DECLS_ONCE([alarm]) +AC_REQUIRE([gl_SEMAPHORE]) + +Makefile.am: +TESTS += test-spin1 test-spin2 +check_PROGRAMS += test-spin1 test-spin2 +test_spin2_LDADD = $(LDADD) @LIBMULTITHREAD@ @YIELD_LIB@ @LIB_SEMAPHORE@ diff --git a/tests/test-spin1.c b/tests/test-spin1.c new file mode 100644 index 0000000000..5b82925905 --- /dev/null +++ b/tests/test-spin1.c @@ -0,0 +1,52 @@ +/* Test of spin locks in multithreaded situations. + Copyright (C) 2005, 2008-2025 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 "glthread/spin.h" + +gl_spinlock_define_initialized (, global_spin_lock) + +int +main (void) +{ + /* Check a spin-lock initialized through the constant initializer. */ + { + gl_spinlock_lock (global_spin_lock); + gl_spinlock_unlock (global_spin_lock); + } + + /* Check a spin-lock initialized through gl_spinlock_init. */ + { + gl_spinlock_define (, local_spin_lock) + int i; + + gl_spinlock_init (local_spin_lock); + + for (i = 0; i < 10; i++) + { + gl_spinlock_lock (local_spin_lock); + gl_spinlock_unlock (local_spin_lock); + } + + gl_spinlock_destroy (local_spin_lock); + } + + return 0; +} diff --git a/tests/test-spin2.c b/tests/test-spin2.c new file mode 100644 index 0000000000..b3baf3c325 --- /dev/null +++ b/tests/test-spin2.c @@ -0,0 +1,240 @@ +/* Test of spin locks in multithreaded situations. + Copyright (C) 2005, 2008-2025 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) || __clang_major__ >= 3) && !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 "glthread/spin.h" +#if !ENABLE_LOCKING +# undef gl_spinlock_init +# define gl_spinlock_init(lock) (void)(lock) +# undef gl_spinlock_lock +# define gl_spinlock_lock(lock) (void)(lock) +# undef gl_spinlock_unlock +# define gl_spinlock_unlock(lock) (void)(lock) +# undef gl_spinlock_destroy +# define gl_spinlock_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 + +#define ACCOUNT_COUNT 4 + +static int account[ACCOUNT_COUNT]; + +static int +random_account (void) +{ + return ((unsigned long) random () >> 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. */ + +gl_spinlock_define (static, my_lock) + +static void * +lock_mutator_thread (void *arg) +{ + int repeat; + + for (repeat = REPEAT_COUNT; repeat > 0; repeat--) + { + int i1, i2, value; + + dbgprintf ("Mutator %p before lock\n", gl_thread_self_pointer ()); + gl_spinlock_lock (my_lock); + dbgprintf ("Mutator %p after lock\n", gl_thread_self_pointer ()); + + i1 = random_account (); + i2 = random_account (); + value = ((unsigned long) random () >> 3) % 10; + account[i1] += value; + account[i2] -= value; + + dbgprintf ("Mutator %p before unlock\n", gl_thread_self_pointer ()); + gl_spinlock_unlock (my_lock); + dbgprintf ("Mutator %p after unlock\n", gl_thread_self_pointer ()); + + dbgprintf ("Mutator %p before check lock\n", gl_thread_self_pointer ()); + gl_spinlock_lock (my_lock); + check_accounts (); + gl_spinlock_unlock (my_lock); + 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) + { + dbgprintf ("Checker %p before check lock\n", gl_thread_self_pointer ()); + gl_spinlock_lock (my_lock); + check_accounts (); + gl_spinlock_unlock (my_lock); + 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 + + gl_spinlock_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.43.0
>From aba715840c0db7ad2eac29c6be1cb621d8bd14ec Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Wed, 14 May 2025 16:27:30 +0200 Subject: [PATCH 3/3] asyncsafe-spin: Rely on module 'spin'. * lib/asyncsafe-spin.h: Include glthread/spin.h. (asyncsafe_spinlock_t, ASYNCSAFE_SPIN_INIT): Simplify by using gl_spinlock_t, gl_spinlock_initializer. * lib/asyncsafe-spin.c: Simplify by using glthread_spin_* functions. * modules/asyncsafe-spin (Files): Remove m4/atomic-cas.m4. (Depends-on): Add spin. Remove bool, windows-spin, sparcv8+. (configure.ac): Remove tests. --- ChangeLog | 11 ++ lib/asyncsafe-spin.c | 319 ++--------------------------------------- lib/asyncsafe-spin.h | 12 +- modules/asyncsafe-spin | 8 +- 4 files changed, 25 insertions(+), 325 deletions(-) diff --git a/ChangeLog b/ChangeLog index b903bacf5e..371af7fada 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2025-05-14 Bruno Haible <br...@clisp.org> + + asyncsafe-spin: Rely on module 'spin'. + * lib/asyncsafe-spin.h: Include glthread/spin.h. + (asyncsafe_spinlock_t, ASYNCSAFE_SPIN_INIT): Simplify by using + gl_spinlock_t, gl_spinlock_initializer. + * lib/asyncsafe-spin.c: Simplify by using glthread_spin_* functions. + * modules/asyncsafe-spin (Files): Remove m4/atomic-cas.m4. + (Depends-on): Add spin. Remove bool, windows-spin, sparcv8+. + (configure.ac): Remove tests. + 2025-05-14 Bruno Haible <br...@clisp.org> spin: Add tests. diff --git a/lib/asyncsafe-spin.c b/lib/asyncsafe-spin.c index e54f43ca7d..5dbefa1c60 100644 --- a/lib/asyncsafe-spin.c +++ b/lib/asyncsafe-spin.c @@ -22,332 +22,31 @@ #include "asyncsafe-spin.h" #include <stdlib.h> -#if defined _AIX -# include <sys/atomic_op.h> -#endif -#if 0x590 <= __SUNPRO_C && __STDC__ -# define asm __asm -#endif - -#if defined _WIN32 && ! defined __CYGWIN__ -/* Use Windows threads. */ - -void -asyncsafe_spin_init (asyncsafe_spinlock_t *lock) -{ - glwthread_spin_init (lock); -} - -static inline void -do_lock (asyncsafe_spinlock_t *lock) -{ - glwthread_spin_lock (lock); -} - -static inline void -do_unlock (asyncsafe_spinlock_t *lock) -{ - if (glwthread_spin_unlock (lock)) - abort (); -} - -void -asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock) -{ - glwthread_spin_destroy (lock); -} - -#else - -# if 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) \ - || __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 1)) \ - && !defined __ibmxl__ -/* Use GCC built-ins (available in GCC >= 4.7 and clang >= 3.1) 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); + glthread_spinlock_init (lock); } -static inline void -do_lock (asyncsafe_spinlock_t *lock) -{ - /* 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))) - ; -} - -static inline void -do_unlock (asyncsafe_spinlock_t *lock) -{ - /* 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 (); -} - -# 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); -} - -static inline void -do_lock (asyncsafe_spinlock_t *lock) -{ - while (__atomic_test_and_set (lock, __ATOMIC_SEQ_CST)) - ; -} - -static inline void -do_unlock (asyncsafe_spinlock_t *lock) -{ - __atomic_clear (lock, __ATOMIC_SEQ_CST); -} - -# endif - -# elif (((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) \ - || __clang_major__ >= 3) \ - && HAVE_ATOMIC_COMPARE_AND_SWAP_GCC41) -/* Use GCC built-ins (available on many platforms with GCC >= 4.1 or - clang >= 3.0). - 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 (); -} - -static inline void -do_lock (asyncsafe_spinlock_t *lock) -{ - /* Wait until *lock becomes 0, then replace it with 1. */ - while (__sync_val_compare_and_swap (lock, 0, 1) != 0) - ; -} - -static inline void -do_unlock (asyncsafe_spinlock_t *lock) -{ - /* If *lock is 1, then replace it with 0. */ - if (__sync_val_compare_and_swap (lock, 1, 0) != 1) - abort (); -} - -# elif defined _AIX -/* AIX */ - -void -asyncsafe_spin_init (asyncsafe_spinlock_t *lock) -{ - atomic_p vp = (int *) lock; - _clear_lock (vp, 0); -} - -static inline void -do_lock (asyncsafe_spinlock_t *lock) -{ - atomic_p vp = (int *) lock; - while (_check_lock (vp, 0, 1)) - ; -} - -static inline void -do_unlock (asyncsafe_spinlock_t *lock) -{ - atomic_p vp = (int *) lock; - if (_check_lock (vp, 1, 0)) - abort (); -} - -# elif ((defined __GNUC__ || defined __clang__ || defined __SUNPRO_C) && (defined __sparc || defined __i386 || defined __x86_64__)) || (defined __TINYC__ && (defined __i386 || defined __x86_64__)) -/* For older versions of GCC or clang, use inline assembly. - GCC, clang, and the Oracle Studio C 12 compiler understand GCC's extended - asm syntax, but the plain Oracle Studio C 11 compiler understands only - simple asm. */ -/* An implementation that verifies the unlocks. */ - -static void -memory_barrier (void) -{ -# if defined __GNUC__ || defined __clang__ || __SUNPRO_C >= 0x590 || defined __TINYC__ -# if defined __i386 || defined __x86_64__ -# if defined __TINYC__ && defined __i386 - /* Cannot use the SSE instruction "mfence" with this compiler. */ - asm volatile ("lock orl $0,(%esp)"); -# else - asm volatile ("mfence"); -# endif -# 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) +asyncsafe_spin_lock (asyncsafe_spinlock_t *lock, + const sigset_t *mask, sigset_t *saved_mask) { -# if defined __GNUC__ || defined __clang__ || __SUNPRO_C >= 0x590 || defined __TINYC__ - 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 + sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */ + glthread_spinlock_lock (lock); } void -asyncsafe_spin_init (asyncsafe_spinlock_t *lock) -{ - volatile unsigned int *vp = lock; - *vp = 0; - memory_barrier (); -} - -static inline void -do_lock (asyncsafe_spinlock_t *lock) -{ - volatile unsigned int *vp = lock; - while (atomic_compare_and_swap (vp, 0, 1) != 0) - ; -} - -static inline void -do_unlock (asyncsafe_spinlock_t *lock) +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) + if (glthread_spinlock_unlock (lock)) abort (); + sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */ } -# else -/* Fallback code. It has some race conditions. */ - -void -asyncsafe_spin_init (asyncsafe_spinlock_t *lock) -{ - volatile unsigned int *vp = lock; - *vp = 0; -} - -static inline void -do_lock (asyncsafe_spinlock_t *lock) -{ - volatile unsigned int *vp = lock; - while (*vp) - ; - *vp = 1; -} - -static inline void -do_unlock (asyncsafe_spinlock_t *lock) -{ - volatile unsigned int *vp = lock; - *vp = 0; -} - -# endif - -# else -/* Provide a dummy implementation for single-threaded applications. */ - -void -asyncsafe_spin_init (asyncsafe_spinlock_t *lock) -{ -} - -static inline void -do_lock (asyncsafe_spinlock_t *lock) -{ -} - -static inline void -do_unlock (asyncsafe_spinlock_t *lock) -{ -} - -# endif - void asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock) { -} - -#endif - -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 */ - do_lock (lock); -} - -void -asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask) -{ - do_unlock (lock); - sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */ + glthread_spinlock_destroy (lock); } diff --git a/lib/asyncsafe-spin.h b/lib/asyncsafe-spin.h index 8a8963877d..1bbf2f4702 100644 --- a/lib/asyncsafe-spin.h +++ b/lib/asyncsafe-spin.h @@ -42,14 +42,10 @@ #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 +#include "glthread/spin.h" + +typedef gl_spinlock_t asyncsafe_spinlock_t; +#define ASYNCSAFE_SPIN_INIT gl_spinlock_initializer #ifdef __cplusplus extern "C" { diff --git a/modules/asyncsafe-spin b/modules/asyncsafe-spin index 3ca67464ef..d918096163 100644 --- a/modules/asyncsafe-spin +++ b/modules/asyncsafe-spin @@ -4,19 +4,13 @@ Spin locks for communication between threads and signal handlers. Files: lib/asyncsafe-spin.h lib/asyncsafe-spin.c -m4/atomic-cas.m4 Depends-on: signal-h -bool sigprocmask -windows-spin -sparcv8+ +spin configure.ac: -AC_REQUIRE([AC_C_INLINE]) -AC_CHECK_HEADERS_ONCE([pthread.h]) -AC_REQUIRE([gl_ATOMIC_COMPARE_AND_SWAP]) Makefile.am: lib_SOURCES += asyncsafe-spin.c -- 2.43.0