]> Savannah Git Hosting - gnulib.git/commitdiff
pthread-once: Work around Cygwin 3.5.3 bug.
authorBruno Haible <bruno@clisp.org>
Tue, 28 May 2024 20:35:54 +0000 (22:35 +0200)
committerBruno Haible <bruno@clisp.org>
Fri, 31 May 2024 22:08:42 +0000 (00:08 +0200)
* 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.

ChangeLog
doc/posix-functions/pthread_once.texi
lib/pthread-once.c
m4/pthread-once.m4

index e368386dac8a03641c9d538dae15cd4a124a4f84..965f6d193a168eb0986a77405b67e385109789e3 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2024-05-31  Bruno Haible  <bruno@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.
+
 2024-05-29  Bruno Haible  <bruno@clisp.org>
 
        call_once tests: Fix link error on mingw.
index 8bae5eaf10d8b28df4676279d69ff37ce93de9cc..682e35fa6e51bdc1721f0cc1287d7c8a61dab77e 100644 (file)
@@ -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:
index 1954bd38939b7b98b3ddd840e76821177e5c9ebf..4b4a18d2afbb915f8f97ac3ff981f519acaa5996 100644 (file)
@@ -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 <stdlib.h>
+
+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 = (<number of waiting threads> << 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.  */
 
index deac4609fc5e8362341868df9af012ffe289107e..0f28377f8269597d9d84afda88a4253ab9289491 100644 (file)
@@ -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
 ])