On Cygwin 3.5.3, programs that use pthread_once or call_once may hang indefinitely.
Reported at <https://cygwin.com/pipermail/cygwin/2024-May/255987.html>. This patch provides a workaround, that makes test-pthread-once2 and test-lock succeed. I did this workaround because - This Cygwin version is what the GitHub CI uses currently. - There's no sign of activity regarding Cygwin from Corinna Vinschen in more than a month. 2024-05-28 Bruno Haible <br...@clisp.org> pthread-once: Work around Cygwin 3.5.3 bug. * m4/pthread-once.m4 (gl_PTHREAD_ONCE): On Cygwin, set REPLACE_PTHREAD_ONCE to 1. * lib/pthread-once.c (pthread_once): Add an implementation for Cygwin. * doc/posix-functions/pthread_once.texi: Mention the Cygwin bug. diff --git a/doc/posix-functions/pthread_once.texi b/doc/posix-functions/pthread_once.texi index 8bae5eaf10..682e35fa6e 100644 --- a/doc/posix-functions/pthread_once.texi +++ b/doc/posix-functions/pthread_once.texi @@ -13,6 +13,10 @@ Minix 3.1.8, mingw, MSVC 14. But the provided replacement is just a dummy on some of these platforms: Minix 3.1.8. +@item +This function makes applications hang forever on some platforms: +@c https://cygwin.com/pipermail/cygwin/2024-May/255987.html +Cygwin 3.5.3. @end itemize Portability problems not fixed by Gnulib: diff --git a/lib/pthread-once.c b/lib/pthread-once.c index c20d76f930..069e77e3f3 100644 --- a/lib/pthread-once.c +++ b/lib/pthread-once.c @@ -38,6 +38,98 @@ pthread_once (pthread_once_t *once_control, void (*initfunction) (void)) #elif HAVE_PTHREAD_H /* Provide workarounds for POSIX threads. */ +# if defined __CYGWIN__ + +# include <stdlib.h> + +int +pthread_once (pthread_once_t *once_control, void (*initfunction) (void)) +{ +# if 0 + /* This would be the code, for + typedef struct + { + pthread_mutex_t mutex; + _Atomic unsigned int num_threads; + _Atomic unsigned int done; + } + pthread_once_t; + */ + if (once_control->done == 0) + { + once_control->num_threads += 1; + pthread_mutex_lock (&once_control->mutex); + if (once_control->done == 0) + { + (*initfunction) (); + once_control->done = 1; + } + pthread_mutex_unlock (&once_control->mutex); + if ((once_control->num_threads -= 1) == 0) + pthread_mutex_destroy (&once_control->mutex); + } +# else + /* In this implementation, we reuse the type + typedef struct { pthread_mutex_t mutex; int state; } pthread_once_t; + #define PTHREAD_ONCE_INIT { PTHREAD_MUTEX_INITIALIZER, 0 } + while assigning the following meaning to the state: + state = 2 * <number of waiting threads> + <1 if done> + In other words: + state = { unsigned int num_threads : 31; unsigned int done : 1; } + */ + /* Test the 'done' bit. */ + if ((* (unsigned int volatile *) &once_control->state & 1) == 0) + { + /* The 'done' bit is still zero. Increment num_threads (atomically). */ + for (;;) + { + unsigned int prev = * (unsigned int volatile *) &once_control->state; + if (__sync_bool_compare_and_swap ((unsigned int *) &once_control->state, + prev, prev + 2)) + break; + } + /* We have incremented num_threads. Now take the lock. */ + pthread_mutex_lock (&once_control->mutex); + /* Test the 'done' bit again. */ + if ((* (unsigned int volatile *) &once_control->state & 1) == 0) + { + /* Execute the initfunction. */ + (*initfunction) (); + /* Set the 'done' bit to 1 (atomically). */ + for (;;) + { + unsigned int prev = * (unsigned int volatile *) &once_control->state; + if (__sync_bool_compare_and_swap ((unsigned int *) &once_control->state, + prev, prev | 1)) + break; + } + } + /* Now the 'done' bit is 1. Release the lock. */ + pthread_mutex_unlock (&once_control->mutex); + /* Decrement num_threads (atomically). */ + for (;;) + { + unsigned int prev = * (unsigned int volatile *) &once_control->state; + if (prev < 2) + abort (); + if (__sync_bool_compare_and_swap ((unsigned int *) &once_control->state, + prev, prev - 2)) + { + if (prev - 2 == 1) + /* num_threads is now zero, and done is 1. + No other thread will need to use the lock. + We can therefore destroy the lock, to free resources. */ + pthread_mutex_destroy (&once_control->mutex); + break; + } + } + } +# endif + return 0; +} + +# endif + #else /* Provide a dummy implementation for single-threaded applications. */ diff --git a/m4/pthread-once.m4 b/m4/pthread-once.m4 index a0a9d372b3..46b8840e2b 100644 --- a/m4/pthread-once.m4 +++ b/m4/pthread-once.m4 @@ -1,5 +1,5 @@ # pthread-once.m4 -# serial 2 +# serial 3 dnl Copyright (C) 2019-2024 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -18,6 +18,19 @@ AC_DEFUN([gl_PTHREAD_ONCE] else if test $HAVE_PTHREAD_H = 0; then HAVE_PTHREAD_ONCE=0 + else + dnl Work around Cygwin 3.5.3 bug. + AC_CACHE_CHECK([whether pthread_once works], + [gl_cv_func_pthread_once_works], + [case "$host_os" in + cygwin*) gl_cv_func_pthread_once_works="guessing no" ;; + *) gl_cv_func_pthread_once_works="yes" ;; + esac + ]) + case "$gl_cv_func_pthread_once_works" in + *yes) ;; + *) REPLACE_PTHREAD_ONCE=1 ;; + esac fi fi ])