summaryrefslogtreecommitdiff
path: root/gnulib-m4/pthread-rwlock.m4
diff options
context:
space:
mode:
Diffstat (limited to 'gnulib-m4/pthread-rwlock.m4')
-rw-r--r--gnulib-m4/pthread-rwlock.m4461
1 files changed, 461 insertions, 0 deletions
diff --git a/gnulib-m4/pthread-rwlock.m4 b/gnulib-m4/pthread-rwlock.m4
new file mode 100644
index 00000000..0e203606
--- /dev/null
+++ b/gnulib-m4/pthread-rwlock.m4
@@ -0,0 +1,461 @@
+# pthread-rwlock.m4
+# serial 7
+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.
+
+AC_DEFUN([gl_PTHREAD_RWLOCK],
+[
+ AC_REQUIRE([gl_PTHREAD_H])
+ AC_REQUIRE([AC_CANONICAL_HOST])
+
+ if { case "$host_os" in mingw* | windows*) true;; *) false;; esac; } \
+ && test $gl_threads_api = windows; then
+ dnl Choose function names that don't conflict with the mingw-w64 winpthreads
+ dnl library.
+ REPLACE_PTHREAD_RWLOCK_INIT=1
+ REPLACE_PTHREAD_RWLOCKATTR_INIT=1
+ REPLACE_PTHREAD_RWLOCKATTR_DESTROY=1
+ REPLACE_PTHREAD_RWLOCK_RDLOCK=1
+ REPLACE_PTHREAD_RWLOCK_WRLOCK=1
+ REPLACE_PTHREAD_RWLOCK_TRYRDLOCK=1
+ REPLACE_PTHREAD_RWLOCK_TRYWRLOCK=1
+ REPLACE_PTHREAD_RWLOCK_TIMEDRDLOCK=1
+ REPLACE_PTHREAD_RWLOCK_TIMEDWRLOCK=1
+ REPLACE_PTHREAD_RWLOCK_UNLOCK=1
+ REPLACE_PTHREAD_RWLOCK_DESTROY=1
+ else
+ if test $HAVE_PTHREAD_H = 0; then
+ HAVE_PTHREAD_RWLOCK_INIT=0
+ HAVE_PTHREAD_RWLOCKATTR_INIT=0
+ HAVE_PTHREAD_RWLOCKATTR_DESTROY=0
+ HAVE_PTHREAD_RWLOCK_RDLOCK=0
+ HAVE_PTHREAD_RWLOCK_WRLOCK=0
+ HAVE_PTHREAD_RWLOCK_TRYRDLOCK=0
+ HAVE_PTHREAD_RWLOCK_TRYWRLOCK=0
+ HAVE_PTHREAD_RWLOCK_TIMEDRDLOCK=0
+ HAVE_PTHREAD_RWLOCK_TIMEDWRLOCK=0
+ HAVE_PTHREAD_RWLOCK_UNLOCK=0
+ HAVE_PTHREAD_RWLOCK_DESTROY=0
+ else
+ dnl On Mac OS X 10.4, the pthread_rwlock_* functions exist but are not
+ dnl usable because PTHREAD_RWLOCK_INITIALIZER is not defined.
+ dnl On Android 4.3, the pthread_rwlock_* functions are declared in
+ dnl <pthread.h> but don't exist in libc.
+ AC_CACHE_CHECK([for pthread_rwlock_init],
+ [gl_cv_func_pthread_rwlock_init],
+ [case "$host_os" in
+ darwin*)
+ AC_COMPILE_IFELSE(
+ [AC_LANG_SOURCE(
+ [[#include <pthread.h>
+ pthread_rwlock_t l = PTHREAD_RWLOCK_INITIALIZER;
+ ]])],
+ [gl_cv_func_pthread_rwlock_init=yes],
+ [gl_cv_func_pthread_rwlock_init=no])
+ ;;
+ *)
+ saved_LIBS="$LIBS"
+ LIBS="$LIBS $LIBPMULTITHREAD"
+ AC_LINK_IFELSE(
+ [AC_LANG_SOURCE(
+ [[extern
+ #ifdef __cplusplus
+ "C"
+ #endif
+ int pthread_rwlock_init (void);
+ int main ()
+ {
+ return pthread_rwlock_init ();
+ }
+ ]])],
+ [gl_cv_func_pthread_rwlock_init=yes],
+ [gl_cv_func_pthread_rwlock_init=no])
+ LIBS="$saved_LIBS"
+ ;;
+ esac
+ ])
+ if test $gl_cv_func_pthread_rwlock_init = no; then
+ REPLACE_PTHREAD_RWLOCK_INIT=1
+ REPLACE_PTHREAD_RWLOCKATTR_INIT=1
+ REPLACE_PTHREAD_RWLOCKATTR_DESTROY=1
+ REPLACE_PTHREAD_RWLOCK_RDLOCK=1
+ REPLACE_PTHREAD_RWLOCK_WRLOCK=1
+ REPLACE_PTHREAD_RWLOCK_TRYRDLOCK=1
+ REPLACE_PTHREAD_RWLOCK_TRYWRLOCK=1
+ REPLACE_PTHREAD_RWLOCK_TIMEDRDLOCK=1
+ REPLACE_PTHREAD_RWLOCK_TIMEDWRLOCK=1
+ REPLACE_PTHREAD_RWLOCK_UNLOCK=1
+ REPLACE_PTHREAD_RWLOCK_DESTROY=1
+ AC_DEFINE([PTHREAD_RWLOCK_UNIMPLEMENTED], [1],
+ [Define if all pthread_rwlock* functions don't exist.])
+ else
+ dnl On Mac OS X 10.5, FreeBSD 5.2.1, OpenBSD 3.8, AIX 5.1, HP-UX 11,
+ dnl IRIX 6.5, Solaris 9, Cygwin, the pthread_rwlock_timed*lock functions
+ dnl don't exist, although the other pthread_rwlock* functions exist.
+ AC_CHECK_DECL([pthread_rwlock_timedrdlock], ,
+ [HAVE_PTHREAD_RWLOCK_TIMEDRDLOCK=0
+ HAVE_PTHREAD_RWLOCK_TIMEDWRLOCK=0
+ AC_DEFINE([PTHREAD_RWLOCK_LACKS_TIMEOUT], [1],
+ [Define if the functions pthread_rwlock_timedrdlock and pthread_rwlock_timedwrlock don't exist.])
+ ],
+ [[#include <pthread.h>]])
+ dnl In glibc ≥ 2.25 on Linux, test-pthread-rwlock-waitqueue reports
+ dnl "This implementation always prefers readers.", and this wait queue
+ dnl handling is unsuitable, because it leads to writer starvation:
+ dnl On machines with 8 or more CPUs, test-pthread-rwlock may never
+ dnl terminate. See
+ dnl <https://lists.gnu.org/archive/html/bug-gnulib/2024-06/msg00291.html>
+ dnl <https://lists.gnu.org/archive/html/bug-gnulib/2024-07/msg00081.html>
+ dnl for details.
+ AC_CACHE_CHECK([for reasonable pthread_rwlock wait queue handling],
+ [gl_cv_func_pthread_rwlock_good_waitqueue],
+ [case "$host_os" in
+ linux*-gnu*)
+ saved_LIBS="$LIBS"
+ LIBS="$LIBS $LIBPMULTITHREAD"
+ AC_RUN_IFELSE(
+ [AC_LANG_SOURCE([[
+/* This test is a simplified variant of tests/test-pthread-rwlock-waitqueue.c. */
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#if defined __hppa
+# define STEP_INTERVAL 20000000 /* nanoseconds */
+#else
+# define STEP_INTERVAL 10000000 /* nanoseconds */
+#endif
+
+static pthread_rwlock_t lock;
+
+static pthread_rwlock_t sprintf_lock;
+
+struct locals
+{
+ const char *name;
+ unsigned int wait_before;
+ unsigned int wait_after;
+ char *result;
+};
+
+static void *
+reader_func (void *arg)
+{
+ struct locals *l = arg;
+ int err;
+
+ if (l->wait_before > 0)
+ {
+ struct timespec duration;
+ duration.tv_sec = l->wait_before / 1000000000;
+ duration.tv_nsec = l->wait_before % 1000000000;
+ nanosleep (&duration, NULL);
+ }
+ err = pthread_rwlock_rdlock (&lock);
+ if (err)
+ {
+ fprintf (stderr, "pthread_rwlock_rdlock failed, error = %d\n", err);
+ abort ();
+ }
+ if (pthread_rwlock_wrlock (&sprintf_lock))
+ {
+ fprintf (stderr, "pthread_rwlock_wrlock on sprintf_lock failed\n");
+ abort ();
+ }
+ sprintf (l->result + strlen (l->result), " %s", l->name);
+ if (pthread_rwlock_unlock (&sprintf_lock))
+ {
+ fprintf (stderr, "pthread_rwlock_unlock on sprintf_lock failed\n");
+ abort ();
+ }
+ if (l->wait_after > 0)
+ {
+ struct timespec duration;
+ duration.tv_sec = l->wait_after / 1000000000;
+ duration.tv_nsec = l->wait_after % 1000000000;
+ nanosleep (&duration, NULL);
+ }
+ err = pthread_rwlock_unlock (&lock);
+ if (err)
+ {
+ fprintf (stderr, "pthread_rwlock_unlock failed, error = %d\n", err);
+ abort ();
+ }
+
+ return NULL;
+}
+
+static void *
+writer_func (void *arg)
+{
+ struct locals *l = arg;
+ int err;
+
+ if (l->wait_before > 0)
+ {
+ struct timespec duration;
+ duration.tv_sec = l->wait_before / 1000000000;
+ duration.tv_nsec = l->wait_before % 1000000000;
+ nanosleep (&duration, NULL);
+ }
+ err = pthread_rwlock_wrlock (&lock);
+ if (err)
+ {
+ fprintf (stderr, "pthread_rwlock_rdlock failed, error = %d\n", err);
+ abort ();
+ }
+ if (pthread_rwlock_wrlock (&sprintf_lock))
+ {
+ fprintf (stderr, "pthread_rwlock_wrlock on sprintf_lock failed\n");
+ abort ();
+ }
+ sprintf (l->result + strlen (l->result), " %s", l->name);
+ if (pthread_rwlock_unlock (&sprintf_lock))
+ {
+ fprintf (stderr, "pthread_rwlock_unlock on sprintf_lock failed\n");
+ abort ();
+ }
+ if (l->wait_after > 0)
+ {
+ struct timespec duration;
+ duration.tv_sec = l->wait_after / 1000000000;
+ duration.tv_nsec = l->wait_after % 1000000000;
+ nanosleep (&duration, NULL);
+ }
+ err = pthread_rwlock_unlock (&lock);
+ if (err)
+ {
+ fprintf (stderr, "pthread_rwlock_unlock failed, error = %d\n", err);
+ abort ();
+ }
+
+ return NULL;
+}
+
+static const char *
+do_test (const char *rw_string)
+{
+ size_t n = strlen (rw_string);
+ int err;
+ char resultbuf[100];
+
+ char **names = (char **) malloc (n * sizeof (char *));
+ for (size_t i = 0; i < n; i++)
+ {
+ char name[12];
+ sprintf (name, "%c%u", rw_string[i], (unsigned int) (i+1));
+ names[i] = strdup (name);
+ }
+
+ resultbuf[0] = '\0';
+
+ /* Create the threads. */
+ struct locals *locals = (struct locals *) malloc (n * sizeof (struct locals));
+ pthread_t *threads = (pthread_t *) malloc (n * sizeof (pthread_t));
+ for (size_t i = 0; i < n; i++)
+ {
+ locals[i].name = names[i];
+ locals[i].wait_before = i * STEP_INTERVAL;
+ locals[i].wait_after = (i == 0 ? n * STEP_INTERVAL : 0);
+ locals[i].result = resultbuf;
+ err = pthread_create (&threads[i], NULL,
+ rw_string[i] == 'R' ? reader_func :
+ rw_string[i] == 'W' ? writer_func :
+ (abort (), NULL),
+ &locals[i]);
+ if (err)
+ {
+ fprintf (stderr, "pthread_create failed to create thread %u, error = %d\n",
+ (unsigned int) (i+1), err);
+ abort ();
+ }
+ }
+
+ /* Wait until the threads are done. */
+ for (size_t i = 0; i < n; i++)
+ {
+ void *retcode;
+ err = pthread_join (threads[i], &retcode);
+ if (err)
+ {
+ fprintf (stderr, "pthread_join failed to wait for thread %u, error = %d\n",
+ (unsigned int) (i+1), err);
+ abort ();
+ }
+ }
+
+ /* Clean up. */
+ free (threads);
+ free (locals);
+ for (size_t i = 0; i < n; i++)
+ free (names[i]);
+ free (names);
+
+ return strdup (resultbuf);
+}
+
+static bool
+startswith (const char *str, const char *prefix)
+{
+ return strncmp (str, prefix, strlen (prefix)) == 0;
+}
+
+static int
+find_wait_queue_handling (void)
+{
+ bool final_r_prefers_readers = true;
+ bool final_w_prefers_readers = true;
+
+ /* Perform the test a few times, so that in case of a non-deterministic
+ behaviour that happens to look like deterministic in one round, we get
+ a higher probability of finding that it is non-deterministic. */
+ for (int repeat = 3; repeat > 0; repeat--)
+ {
+ bool r_prefers_readers = false;
+ bool w_prefers_readers = false;
+
+ {
+ const char * RWR = do_test ("RWR");
+ const char * RWRR = do_test ("RWRR");
+ const char * RWRW = do_test ("RWRW");
+ const char * RWWR = do_test ("RWWR");
+ const char * RWRRR = do_test ("RWRRR");
+ const char * RWRRW = do_test ("RWRRW");
+ const char * RWRWR = do_test ("RWRWR");
+ const char * RWRWW = do_test ("RWRWW");
+ const char * RWWRR = do_test ("RWWRR");
+ const char * RWWRW = do_test ("RWWRW");
+ const char * RWWWR = do_test ("RWWWR");
+
+ if ( startswith (RWR, " R1 R")
+ && startswith (RWRR, " R1 R")
+ && startswith (RWRW, " R1 R")
+ && startswith (RWWR, " R1 R")
+ && startswith (RWRRR, " R1 R")
+ && startswith (RWRRW, " R1 R")
+ && startswith (RWRWR, " R1 R")
+ && startswith (RWRWW, " R1 R")
+ && startswith (RWWRR, " R1 R")
+ && startswith (RWWRW, " R1 R")
+ && startswith (RWWWR, " R1 R"))
+ r_prefers_readers = true;
+ }
+
+ {
+ const char * WRR = do_test ("WRR");
+ const char * WRW = do_test ("WRW");
+ const char * WWR = do_test ("WWR");
+ const char * WRRR = do_test ("WRRR");
+ const char * WRRW = do_test ("WRRW");
+ const char * WRWR = do_test ("WRWR");
+ const char * WRWW = do_test ("WRWW");
+ const char * WWRR = do_test ("WWRR");
+ const char * WWRW = do_test ("WWRW");
+ const char * WWWR = do_test ("WWWR");
+ const char * WRRRR = do_test ("WRRRR");
+ const char * WRRRW = do_test ("WRRRW");
+ const char * WRRWR = do_test ("WRRWR");
+ const char * WRRWW = do_test ("WRRWW");
+ const char * WRWRR = do_test ("WRWRR");
+ const char * WRWRW = do_test ("WRWRW");
+ const char * WRWWR = do_test ("WRWWR");
+ const char * WRWWW = do_test ("WRWWW");
+ const char * WWRRR = do_test ("WWRRR");
+ const char * WWRRW = do_test ("WWRRW");
+ const char * WWRWR = do_test ("WWRWR");
+ const char * WWRWW = do_test ("WWRWW");
+ const char * WWWRR = do_test ("WWWRR");
+ const char * WWWRW = do_test ("WWWRW");
+ const char * WWWWR = do_test ("WWWWR");
+
+ if ( startswith (WRR, " W1 R")
+ && startswith (WRW, " W1 R")
+ && startswith (WWR, " W1 R")
+ && startswith (WRRR, " W1 R")
+ && startswith (WRRW, " W1 R")
+ && startswith (WRWR, " W1 R")
+ && startswith (WRWW, " W1 R")
+ && startswith (WWRR, " W1 R")
+ && startswith (WWRW, " W1 R")
+ && startswith (WWWR, " W1 R")
+ && startswith (WRRRR, " W1 R")
+ && startswith (WRRRW, " W1 R")
+ && startswith (WRRWR, " W1 R")
+ && startswith (WRRWW, " W1 R")
+ && startswith (WRWRR, " W1 R")
+ && startswith (WRWRW, " W1 R")
+ && startswith (WRWWR, " W1 R")
+ && startswith (WRWWW, " W1 R")
+ && startswith (WWRRR, " W1 R")
+ && startswith (WWRRW, " W1 R")
+ && startswith (WWRWR, " W1 R")
+ && startswith (WWRWW, " W1 R")
+ && startswith (WWWRR, " W1 R")
+ && startswith (WWWRW, " W1 R")
+ && startswith (WWWWR, " W1 R"))
+ w_prefers_readers = true;
+ }
+
+ final_r_prefers_readers &= r_prefers_readers;
+ final_w_prefers_readers &= w_prefers_readers;
+ }
+
+ /* The wait queue handling is unsuitable if it always prefers readers,
+ because it leads to writer starvation: On machines with 8 or more CPUs,
+ test-pthread-rwlock may never terminate. */
+ return final_r_prefers_readers && final_w_prefers_readers;
+}
+
+int
+main ()
+{
+ /* Initialize the sprintf_lock. */
+ if (pthread_rwlock_init (&sprintf_lock, NULL))
+ {
+ fprintf (stderr, "pthread_rwlock_init failed\n");
+ abort ();
+ }
+
+ /* Find the wait queue handling of a default-initialized lock. */
+ if (pthread_rwlock_init (&lock, NULL))
+ {
+ fprintf (stderr, "pthread_rwlock_init failed\n");
+ abort ();
+ }
+ {
+ int fail = find_wait_queue_handling ();
+ return fail;
+ }
+}
+ ]])
+ ],
+ [gl_cv_func_pthread_rwlock_good_waitqueue=yes],
+ [gl_cv_func_pthread_rwlock_good_waitqueue=no],
+ [dnl Guess no on glibc/Linux.
+ gl_cv_func_pthread_rwlock_good_waitqueue="guessing no"
+ ])
+ LIBS="$saved_LIBS"
+ ;;
+ *) dnl Guess yes on other platforms.
+ gl_cv_func_pthread_rwlock_good_waitqueue="guessing yes"
+ ;;
+ esac
+ ])
+ case "$gl_cv_func_pthread_rwlock_good_waitqueue" in
+ *yes) ;;
+ *no)
+ REPLACE_PTHREAD_RWLOCK_INIT=1
+ REPLACE_PTHREAD_RWLOCKATTR_INIT=1
+ AC_DEFINE([PTHREAD_RWLOCK_BAD_WAITQUEUE], [1],
+ [Define if the pthread_rwlock wait queue handling is not reasonable.])
+ ;;
+ esac
+ fi
+ fi
+ fi
+])