From: Bruno Haible Date: Tue, 28 May 2024 20:35:54 +0000 (+0200) Subject: pthread-once: Work around Cygwin 3.5.3 bug. X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=42f5f346242d7dd39c28874186693dcdf2cc4d61;p=gnulib.git 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/ChangeLog b/ChangeLog index e368386dac..965f6d193a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2024-05-31 Bruno Haible + + 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. + 2024-05-29 Bruno Haible call_once tests: Fix link error on mingw. 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 @@ This function is missing on some platforms: 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 1954bd3893..4b4a18d2af 100644 --- a/lib/pthread-once.c +++ b/lib/pthread-once.c @@ -1,5 +1,5 @@ /* POSIX once-only control. - Copyright (C) 2019-2023 Free Software Foundation, Inc. + Copyright (C) 2019-2024 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 @@ -38,6 +38,99 @@ pthread_once (pthread_once_t *once_control, void (*initfunction) (void)) #elif HAVE_PTHREAD_H /* Provide workarounds for POSIX threads. */ +# if defined __CYGWIN__ + +# include + +int +pthread_once (pthread_once_t *once_control, void (*initfunction) (void)) +{ + /* 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 = ( << 16) + <1 if done> + In other words: + state = { unsigned int num_threads : 16; unsigned int done : 16; } + */ + struct actual_state + { + _Atomic unsigned short num_threads; + /* done == 0: initial state + done == 1: initfunction executed, lock still active + done == 2: initfunction executed, lock no longer usable */ + _Atomic unsigned short done; + }; + struct actual_state *state_p = (struct actual_state *) &once_control->state; + /* This test is not necessary. It's only an optimization, to establish + a fast path for the common case that the 'done' word is already > 0. */ + if (state_p->done == 0) + { + /* Increment num_threads (atomically), to indicate that this thread will + possibly take the lock. */ + state_p->num_threads += 1; + /* Test the 'done' word. */ + if (state_p->done == 0) + { + /* The 'done' word is still zero. Now take the lock. */ + pthread_mutex_lock (&once_control->mutex); + /* Test the 'done' word again. */ + if (state_p->done == 0) + { + /* Execute the initfunction. */ + (*initfunction) (); + /* Set the 'done' word to 1 (atomically). */ + state_p->done = 1; + } + /* Now the 'done' word is 1. Release the lock. */ + pthread_mutex_unlock (&once_control->mutex); + } + /* Here, done is > 0. */ + /* Decrement num_threads (atomically). */ + if ((state_p->num_threads -= 1) == 0) + { + /* num_threads is now zero, and done is > 0. + No other thread will need to use the lock. + We can therefore destroy the lock, to free resources. */ + if (__sync_bool_compare_and_swap (&state_p->done, 1, 2)) + pthread_mutex_destroy (&once_control->mutex); + } + } + /* Proof of correctness: + * num_threads is incremented and then decremented by some threads. + Therefore, num_threads always stays >= 0, and is == 0 at the end. + * The 'done' word, once > 0, stays > 0 (since it is never assigned 0). + * The 'done' word is changed from == 0 to > 0 only while the lock + is taken. Therefore, only the first thread that succeeds in taking + the lock executes the initfunction and sets the 'done' word to a + value > 0; the other threads that take the lock do no side effects + between taking and releasing the lock. + * The 'done' word does not change any more once it is 2. + Therefore, it can be changed from 1 to 2 only once. + * pthread_mutex_destroy gets invoked right after 'done' has been changed + from 1 to 2. Therefore, pthread_mutex_destroy gets invoked only once. + * After a moment where num_threads was 0 and done was > 0, no thread can + reach the pthread_mutex_lock invocation. Proof: + - At such a moment, no thread is in the code range between + state_p->num_threads += 1 + and + state_p->num_threads -= 1 + - After such a moment, some thread can increment num_threads, but from + there they cannot reach the pthread_mutex_lock invocation, because the + if (state_p->done == 0) + test prevents that. + * From this it follows that: + - pthread_mutex_destroy cannot be executed while the lock is taken + (because pthread_mutex_destroy is only executed after a moment where + num_threads was 0 and done was > 0). + - Once pthread_mutex_destroy has been executed, the lock is not used any + more. + */ + 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 deac4609fc..0f28377f82 100644 --- a/m4/pthread-once.m4 +++ b/m4/pthread-once.m4 @@ -1,5 +1,5 @@ -# pthread-once.m4 serial 1 -dnl Copyright (C) 2019-2023 Free Software Foundation, Inc. +# pthread-once.m4 serial 1.1 +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, dnl with or without modifications, as long as this notice is preserved. @@ -17,6 +17,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 ])