diff options
Diffstat (limited to 'gnulib-m4/pthread-rwlock.m4')
-rw-r--r-- | gnulib-m4/pthread-rwlock.m4 | 461 |
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 +]) |