462 lines
15 KiB
Text
462 lines
15 KiB
Text
# pthread-rwlock.m4
|
|
# serial 8
|
|
dnl Copyright (C) 2019-2025 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.
|
|
dnl This file is offered as-is, without any warranty.
|
|
|
|
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 (), (void * (*) (void *)) 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
|
|
])
|