1# pthread_rwlock_rdlock.m4 serial 4
2dnl Copyright (C) 2017-2020 Free Software Foundation, Inc.
3dnl This file is free software; the Free Software Foundation
4dnl gives unlimited permission to copy and/or distribute it,
5dnl with or without modifications, as long as this notice is preserved.
6
7dnl From Bruno Haible.
8dnl Inspired by
9dnl https://github.com/linux-test-project/ltp/blob/master/testcases/open_posix_testsuite/conformance/interfaces/pthread_rwlock_rdlock/2-2.c
10dnl by Intel Corporation.
11
12dnl Test whether in a situation where
13dnl   - an rwlock is taken by a reader and has a writer waiting,
14dnl   - an additional reader requests the lock,
15dnl   - the waiting writer and the requesting reader threads have the same
16dnl     priority,
17dnl the requesting reader thread gets blocked, so that at some point the
18dnl waiting writer can acquire the lock.
19dnl Without such a guarantee, when there a N readers and each of the readers
20dnl spends more than 1/Nth of the time with the lock held, there is a high
21dnl probability that the waiting writer will not get the lock in a given finite
22dnl time, a phenomenon called "writer starvation".
23dnl Without such a guarantee, applications have a hard time avoiding writer
24dnl starvation.
25dnl
26dnl POSIX:2017 makes this requirement only for implementations that support TPS
27dnl (Thread Priority Scheduling) and only for the scheduling policies SCHED_FIFO
28dnl and SCHED_RR, see
29dnl https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_rdlock.html
30dnl but this test verifies the guarantee regardless of TPS and regardless of
31dnl scheduling policy.
32dnl Glibc does not provide this guarantee (and never will on Linux), see
33dnl https://sourceware.org/bugzilla/show_bug.cgi?id=13701
34dnl https://bugzilla.redhat.com/show_bug.cgi?id=1410052
35AC_DEFUN([gl_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER],
36[
37  AC_REQUIRE([gl_THREADLIB_EARLY])
38  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
39  AC_CACHE_CHECK([whether pthread_rwlock_rdlock prefers a writer to a reader],
40    [gl_cv_pthread_rwlock_rdlock_prefer_writer],
41    [save_LIBS="$LIBS"
42     LIBS="$LIBS $LIBMULTITHREAD"
43     AC_RUN_IFELSE(
44       [AC_LANG_SOURCE([[
45#include <errno.h>
46#include <pthread.h>
47#include <stdlib.h>
48#include <unistd.h>
49
50#define SUCCEED() exit (0)
51#define FAILURE() exit (1)
52#define UNEXPECTED(n) (exit (10 + (n)))
53
54/* The main thread creates the waiting writer and the requesting reader threads
55   in the default way; this guarantees that they have the same priority.
56   We can reuse the main thread as first reader thread.  */
57
58static pthread_rwlock_t lock;
59static pthread_t reader1;
60static pthread_t writer;
61static pthread_t reader2;
62static pthread_t timer;
63/* Used to pass control from writer to reader2 and from reader2 to timer,
64   as in a relay race.
65   Passing control from one running thread to another running thread
66   is most likely faster than to create the second thread.  */
67static pthread_mutex_t baton;
68
69static void *
70timer_func (void *ignored)
71{
72  /* Step 13 (can be before or after step 12):
73     The timer thread takes the baton, then waits a moment to make sure
74     it can tell whether the second reader thread is blocked at step 12.  */
75  if (pthread_mutex_lock (&baton))
76    UNEXPECTED (13);
77  usleep (100000);
78  /* By the time we get here, it's clear that the second reader thread is
79     blocked at step 12.  This is the desired behaviour.  */
80  SUCCEED ();
81}
82
83static void *
84reader2_func (void *ignored)
85{
86  int err;
87
88  /* Step 8 (can be before or after step 7):
89     The second reader thread takes the baton, then waits a moment to make sure
90     the writer thread has reached step 7.  */
91  if (pthread_mutex_lock (&baton))
92    UNEXPECTED (8);
93  usleep (100000);
94  /* Step 9: The second reader thread requests the lock.  */
95  err = pthread_rwlock_tryrdlock (&lock);
96  if (err == 0)
97    FAILURE ();
98  else if (err != EBUSY)
99    UNEXPECTED (9);
100  /* Step 10: Launch a timer, to test whether the next call blocks.  */
101  if (pthread_create (&timer, NULL, timer_func, NULL))
102    UNEXPECTED (10);
103  /* Step 11: Release the baton.  */
104  if (pthread_mutex_unlock (&baton))
105    UNEXPECTED (11);
106  /* Step 12: The second reader thread requests the lock.  */
107  err = pthread_rwlock_rdlock (&lock);
108  if (err == 0)
109    FAILURE ();
110  else
111    UNEXPECTED (12);
112}
113
114static void *
115writer_func (void *ignored)
116{
117  /* Step 4: Take the baton, so that the second reader thread does not go ahead
118     too early.  */
119  if (pthread_mutex_lock (&baton))
120    UNEXPECTED (4);
121  /* Step 5: Create the second reader thread.  */
122  if (pthread_create (&reader2, NULL, reader2_func, NULL))
123    UNEXPECTED (5);
124  /* Step 6: Release the baton.  */
125  if (pthread_mutex_unlock (&baton))
126    UNEXPECTED (6);
127  /* Step 7: The writer thread requests the lock.  */
128  if (pthread_rwlock_wrlock (&lock))
129    UNEXPECTED (7);
130  return NULL;
131}
132
133int
134main ()
135{
136  reader1 = pthread_self ();
137
138  /* Step 1: The main thread initializes the lock and the baton.  */
139  if (pthread_rwlock_init (&lock, NULL))
140    UNEXPECTED (1);
141  if (pthread_mutex_init (&baton, NULL))
142    UNEXPECTED (1);
143  /* Step 2: The main thread acquires the lock as a reader.  */
144  if (pthread_rwlock_rdlock (&lock))
145    UNEXPECTED (2);
146  /* Step 3: Create the writer thread.  */
147  if (pthread_create (&writer, NULL, writer_func, NULL))
148    UNEXPECTED (3);
149  /* Job done.  Go to sleep.  */
150  for (;;)
151    {
152      sleep (1);
153    }
154}
155]])],
156       [gl_cv_pthread_rwlock_rdlock_prefer_writer=yes],
157       [gl_cv_pthread_rwlock_rdlock_prefer_writer=no],
158       [case "$host_os" in
159                         # Guess no on glibc systems.
160          *-gnu* | gnu*) gl_cv_pthread_rwlock_rdlock_prefer_writer="guessing no" ;;
161                         # Guess no on musl systems.
162          *-musl*)       gl_cv_pthread_rwlock_rdlock_prefer_writer="guessing no" ;;
163                         # Guess no on bionic systems.
164          *-android*)    gl_cv_pthread_rwlock_rdlock_prefer_writer="guessing no" ;;
165                         # Guess yes on native Windows with the mingw-w64 winpthreads library.
166                         # Guess no on native Windows with the gnulib windows-rwlock module.
167          mingw*)        if test "$gl_use_threads" = yes || test "$gl_use_threads" = posix; then
168                           gl_cv_pthread_rwlock_rdlock_prefer_writer="guessing yes"
169                         else
170                           gl_cv_pthread_rwlock_rdlock_prefer_writer="guessing no"
171                         fi
172                         ;;
173                         # If we don't know, obey --enable-cross-guesses.
174          *)             gl_cv_pthread_rwlock_rdlock_prefer_writer="$gl_cross_guess_normal" ;;
175         esac
176       ])
177     LIBS="$save_LIBS"
178    ])
179  case "$gl_cv_pthread_rwlock_rdlock_prefer_writer" in
180    *yes)
181      AC_DEFINE([HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER], [1],
182        [Define if the 'pthread_rwlock_rdlock' function prefers a writer to a reader.])
183      ;;
184  esac
185])
186