bdc76f
nptl: Fix pthread_rwlock_try*lock stalls (Bug 23844)
bdc76f
bdc76f
For a full analysis of both the pthread_rwlock_tryrdlock() stall
bdc76f
and the pthread_rwlock_trywrlock() stall see:
bdc76f
https://sourceware.org/bugzilla/show_bug.cgi?id=23844#c14
bdc76f
bdc76f
In the pthread_rwlock_trydlock() function we fail to inspect for
bdc76f
PTHREAD_RWLOCK_FUTEX_USED in __wrphase_futex and wake the waiting
bdc76f
readers.
bdc76f
bdc76f
In the pthread_rwlock_trywrlock() function we write 1 to
bdc76f
__wrphase_futex and loose the setting of the PTHREAD_RWLOCK_FUTEX_USED
bdc76f
bit, again failing to wake waiting readers during unlock.
bdc76f
bdc76f
The fix in the case of pthread_rwlock_trydlock() is to check for
bdc76f
PTHREAD_RWLOCK_FUTEX_USED and wake the readers.
bdc76f
bdc76f
The fix in the case of pthread_rwlock_trywrlock() is to only write
bdc76f
1 to __wrphase_futex if we installed the write phase, since all other
bdc76f
readers would be spinning waiting for this step.
bdc76f
bdc76f
We add two new tests, one exercises the stall for
bdc76f
pthread_rwlock_trywrlock() which is easy to exercise, and one exercises
bdc76f
the stall for pthread_rwlock_trydlock() which is harder to exercise.
bdc76f
bdc76f
The pthread_rwlock_trywrlock() test fails consistently without the fix,
bdc76f
and passes after. The pthread_rwlock_tryrdlock() test fails roughly
bdc76f
5-10% of the time without the fix, and passes all the time after.
bdc76f
bdc76f
Signed-off-by: Carlos O'Donell <carlos@redhat.com>
bdc76f
Signed-off-by: Torvald Riegel <triegel@redhat.com>
bdc76f
Signed-off-by: Rik Prohaska <prohaska7@gmail.com>
bdc76f
Co-authored-by: Torvald Riegel <triegel@redhat.com>
bdc76f
Co-authored-by: Rik Prohaska <prohaska7@gmail.com>
bdc76f
(cherry picked from commit 5fc9ed4c4058bfbdf51ad6e7aac7d209b580e8c4)
bdc76f
bdc76f
diff --git a/ChangeLog b/ChangeLog
bdc76f
index 08b42bd2f56471e3..ed1a2ffe8356fd96 100644
bdc76f
--- a/ChangeLog
bdc76f
+++ b/ChangeLog
bdc76f
@@ -1,3 +1,20 @@
bdc76f
+2019-01-31  Carlos O'Donell  <carlos@redhat.com>
bdc76f
+	    Torvald Riegel  <triegel@redhat.com>
bdc76f
+	    Rik Prohaska  <prohaska7@gmail.com>
bdc76f
+
bdc76f
+	[BZ# 23844]
bdc76f
+	* nptl/Makefile (tests): Add tst-rwlock-tryrdlock-stall, and
bdc76f
+	tst-rwlock-trywrlock-stall.
bdc76f
+	* nptl/pthread_rwlock_tryrdlock.c (__pthread_rwlock_tryrdlock):
bdc76f
+	Wake waiters if PTHREAD_RWLOCK_FUTEX_USED is set.
bdc76f
+	* nptl/pthread_rwlock_trywrlock.c (__pthread_rwlock_trywrlock):
bdc76f
+	Set __wrphase_fute to 1 only if we started the write phase.
bdc76f
+	* nptl/tst-rwlock-tryrdlock-stall.c: New file.
bdc76f
+	* nptl/tst-rwlock-trywrlock-stall.c: New file.
bdc76f
+	* support/Makefile (libsupport-routines): Add xpthread_rwlock_destroy.
bdc76f
+	* support/xpthread_rwlock_destroy.c: New file.
bdc76f
+	* support/xthread.h: Declare xpthread_rwlock_destroy.
bdc76f
+
bdc76f
 2018-08-01  Carlos O'Donel  <carlos@redhat.com>
bdc76f
 
bdc76f
 	* version.h (RELEASE): Set to "stable".
bdc76f
diff --git a/nptl/Makefile b/nptl/Makefile
bdc76f
index 2d2db648f730db61..b1003cf56b31ddfa 100644
bdc76f
--- a/nptl/Makefile
bdc76f
+++ b/nptl/Makefile
bdc76f
@@ -319,7 +319,8 @@ tests = tst-attr1 tst-attr2 tst-attr3 tst-default-attr \
bdc76f
 	tst-cnd-basic tst-mtx-trylock tst-cnd-broadcast \
bdc76f
 	tst-cnd-timedwait tst-thrd-detach tst-mtx-basic tst-thrd-sleep \
bdc76f
 	tst-mtx-recursive tst-tss-basic tst-call-once tst-mtx-timedlock \
bdc76f
-	tst-rwlock-pwn
bdc76f
+	tst-rwlock-pwn \
bdc76f
+	tst-rwlock-tryrdlock-stall tst-rwlock-trywrlock-stall
bdc76f
 
bdc76f
 tests-internal := tst-rwlock19 tst-rwlock20 \
bdc76f
 		  tst-sem11 tst-sem12 tst-sem13 \
bdc76f
diff --git a/nptl/pthread_rwlock_tryrdlock.c b/nptl/pthread_rwlock_tryrdlock.c
bdc76f
index 4aec1fc15acb2448..31a88d33a6e8f256 100644
bdc76f
--- a/nptl/pthread_rwlock_tryrdlock.c
bdc76f
+++ b/nptl/pthread_rwlock_tryrdlock.c
bdc76f
@@ -94,15 +94,22 @@ __pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock)
bdc76f
       /* Same as in __pthread_rwlock_rdlock_full:
bdc76f
 	 We started the read phase, so we are also responsible for
bdc76f
 	 updating the write-phase futex.  Relaxed MO is sufficient.
bdc76f
-	 Note that there can be no other reader that we have to wake
bdc76f
-	 because all other readers will see the read phase started by us
bdc76f
-	 (or they will try to start it themselves); if a writer started
bdc76f
-	 the read phase, we cannot have started it.  Furthermore, we
bdc76f
-	 cannot discard a PTHREAD_RWLOCK_FUTEX_USED flag because we will
bdc76f
-	 overwrite the value set by the most recent writer (or the readers
bdc76f
-	 before it in case of explicit hand-over) and we know that there
bdc76f
-	 are no waiting readers.  */
bdc76f
-      atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 0);
bdc76f
+	 We have to do the same steps as a writer would when handing over the
bdc76f
+	 read phase to use because other readers cannot distinguish between
bdc76f
+	 us and the writer.
bdc76f
+	 Note that __pthread_rwlock_tryrdlock callers will not have to be
bdc76f
+	 woken up because they will either see the read phase started by us
bdc76f
+	 or they will try to start it themselves; however, callers of
bdc76f
+	 __pthread_rwlock_rdlock_full just increase the reader count and then
bdc76f
+	 check what state the lock is in, so they cannot distinguish between
bdc76f
+	 us and a writer that acquired and released the lock in the
bdc76f
+	 meantime.  */
bdc76f
+      if ((atomic_exchange_relaxed (&rwlock->__data.__wrphase_futex, 0)
bdc76f
+	  & PTHREAD_RWLOCK_FUTEX_USED) != 0)
bdc76f
+	{
bdc76f
+	  int private = __pthread_rwlock_get_private (rwlock);
bdc76f
+	  futex_wake (&rwlock->__data.__wrphase_futex, INT_MAX, private);
bdc76f
+	}
bdc76f
     }
bdc76f
 
bdc76f
   return 0;
bdc76f
diff --git a/nptl/pthread_rwlock_trywrlock.c b/nptl/pthread_rwlock_trywrlock.c
bdc76f
index 5a73eba756077297..f2e3443466a2554f 100644
bdc76f
--- a/nptl/pthread_rwlock_trywrlock.c
bdc76f
+++ b/nptl/pthread_rwlock_trywrlock.c
bdc76f
@@ -46,8 +46,15 @@ __pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock)
bdc76f
 	  &rwlock->__data.__readers, &r,
bdc76f
 	  r | PTHREAD_RWLOCK_WRPHASE | PTHREAD_RWLOCK_WRLOCKED))
bdc76f
 	{
bdc76f
+	  /* We have become the primary writer and we cannot have shared
bdc76f
+	     the PTHREAD_RWLOCK_FUTEX_USED flag with someone else, so we
bdc76f
+	     can simply enable blocking (see full wrlock code).  */
bdc76f
 	  atomic_store_relaxed (&rwlock->__data.__writers_futex, 1);
bdc76f
-	  atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 1);
bdc76f
+	  /* If we started a write phase, we need to enable readers to
bdc76f
+	     wait.  If we did not, we must not change it because other threads
bdc76f
+	     may have set the PTHREAD_RWLOCK_FUTEX_USED in the meantime.  */
bdc76f
+	  if ((r & PTHREAD_RWLOCK_WRPHASE) == 0)
bdc76f
+	    atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 1);
bdc76f
 	  atomic_store_relaxed (&rwlock->__data.__cur_writer,
bdc76f
 	      THREAD_GETMEM (THREAD_SELF, tid));
bdc76f
 	  return 0;
bdc76f
diff --git a/nptl/tst-rwlock-tryrdlock-stall.c b/nptl/tst-rwlock-tryrdlock-stall.c
bdc76f
new file mode 100644
bdc76f
index 0000000000000000..5e476da2b8d00c6a
bdc76f
--- /dev/null
bdc76f
+++ b/nptl/tst-rwlock-tryrdlock-stall.c
bdc76f
@@ -0,0 +1,355 @@
bdc76f
+/* Bug 23844: Test for pthread_rwlock_tryrdlock stalls.
bdc76f
+   Copyright (C) 2019 Free Software Foundation, Inc.
bdc76f
+   This file is part of the GNU C Library.
bdc76f
+
bdc76f
+   The GNU C Library is free software; you can redistribute it and/or
bdc76f
+   modify it under the terms of the GNU Lesser General Public
bdc76f
+   License as published by the Free Software Foundation; either
bdc76f
+   version 2.1 of the License, or (at your option) any later version.
bdc76f
+
bdc76f
+   The GNU C Library is distributed in the hope that it will be useful,
bdc76f
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
bdc76f
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
bdc76f
+   Lesser General Public License for more details.
bdc76f
+
bdc76f
+   You should have received a copy of the GNU Lesser General Public
bdc76f
+   License along with the GNU C Library; if not, see
bdc76f
+   <http://www.gnu.org/licenses/>.  */
bdc76f
+
bdc76f
+/* For a full analysis see comment:
bdc76f
+   https://sourceware.org/bugzilla/show_bug.cgi?id=23844#c14
bdc76f
+
bdc76f
+   Provided here for reference:
bdc76f
+
bdc76f
+   --- Analysis of pthread_rwlock_tryrdlock() stall ---
bdc76f
+   A read lock begins to execute.
bdc76f
+
bdc76f
+   In __pthread_rwlock_rdlock_full:
bdc76f
+
bdc76f
+   We can attempt a read lock, but find that the lock is
bdc76f
+   in a write phase (PTHREAD_RWLOCK_WRPHASE, or WP-bit
bdc76f
+   is set), and the lock is held by a primary writer
bdc76f
+   (PTHREAD_RWLOCK_WRLOCKED is set). In this case we must
bdc76f
+   wait for explicit hand over from the writer to us or
bdc76f
+   one of the other waiters. The read lock threads are
bdc76f
+   about to execute:
bdc76f
+
bdc76f
+   341   r = (atomic_fetch_add_acquire (&rwlock->__data.__readers,
bdc76f
+   342                                  (1 << PTHREAD_RWLOCK_READER_SHIFT))
bdc76f
+   343        + (1 << PTHREAD_RWLOCK_READER_SHIFT));
bdc76f
+
bdc76f
+   An unlock beings to execute.
bdc76f
+
bdc76f
+   Then in __pthread_rwlock_wrunlock:
bdc76f
+
bdc76f
+   547   unsigned int r = atomic_load_relaxed (&rwlock->__data.__readers);
bdc76f
+   ...
bdc76f
+   549   while (!atomic_compare_exchange_weak_release
bdc76f
+   550          (&rwlock->__data.__readers, &r,
bdc76f
+   551           ((r ^ PTHREAD_RWLOCK_WRLOCKED)
bdc76f
+   552            ^ ((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0 ? 0
bdc76f
+   553               : PTHREAD_RWLOCK_WRPHASE))))
bdc76f
+   554     {
bdc76f
+   ...
bdc76f
+   556     }
bdc76f
+
bdc76f
+   We clear PTHREAD_RWLOCK_WRLOCKED, and if there are
bdc76f
+   no readers so we leave the lock in PTHRAD_RWLOCK_WRPHASE.
bdc76f
+
bdc76f
+   Back in the read lock.
bdc76f
+
bdc76f
+   The read lock adjusts __readres as above.
bdc76f
+
bdc76f
+   383   while ((r & PTHREAD_RWLOCK_WRPHASE) != 0
bdc76f
+   384          && (r & PTHREAD_RWLOCK_WRLOCKED) == 0)
bdc76f
+   385     {
bdc76f
+   ...
bdc76f
+   390       if (atomic_compare_exchange_weak_acquire (&rwlock->__data.__readers, &r,
bdc76f
+   391                                                 r ^ PTHREAD_RWLOCK_WRPHASE))
bdc76f
+   392         {
bdc76f
+
bdc76f
+   And then attemps to start the read phase.
bdc76f
+
bdc76f
+   Assume there happens to be a tryrdlock at this point, noting
bdc76f
+   that PTHREAD_RWLOCK_WRLOCKED is clear, and PTHREAD_RWLOCK_WRPHASE
bdc76f
+   is 1. So the try lock attemps to start the read phase.
bdc76f
+
bdc76f
+   In __pthread_rwlock_tryrdlock:
bdc76f
+
bdc76f
+    44       if ((r & PTHREAD_RWLOCK_WRPHASE) == 0)
bdc76f
+    45         {
bdc76f
+   ...
bdc76f
+    49           if (((r & PTHREAD_RWLOCK_WRLOCKED) != 0)
bdc76f
+    50               && (rwlock->__data.__flags
bdc76f
+    51                   == PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))
bdc76f
+    52             return EBUSY;
bdc76f
+    53           rnew = r + (1 << PTHREAD_RWLOCK_READER_SHIFT);
bdc76f
+    54         }
bdc76f
+   ...
bdc76f
+    89   while (!atomic_compare_exchange_weak_acquire (&rwlock->__data.__readers,
bdc76f
+    90       &r, rnew));
bdc76f
+
bdc76f
+   And succeeds.
bdc76f
+
bdc76f
+   Back in the write unlock:
bdc76f
+
bdc76f
+   557   if ((r >> PTHREAD_RWLOCK_READER_SHIFT) != 0)
bdc76f
+   558     {
bdc76f
+   ...
bdc76f
+   563       if ((atomic_exchange_relaxed (&rwlock->__data.__wrphase_futex, 0)
bdc76f
+   564            & PTHREAD_RWLOCK_FUTEX_USED) != 0)
bdc76f
+   565         futex_wake (&rwlock->__data.__wrphase_futex, INT_MAX, private);
bdc76f
+   566     }
bdc76f
+
bdc76f
+   We note that PTHREAD_RWLOCK_FUTEX_USED is non-zero
bdc76f
+   and don't wake anyone. This is OK because we handed
bdc76f
+   over to the trylock. It will be the trylock's responsibility
bdc76f
+   to wake any waiters.
bdc76f
+
bdc76f
+   Back in the read lock:
bdc76f
+
bdc76f
+   The read lock fails to install PTHRAD_REWLOCK_WRPHASE as 0 because
bdc76f
+   the __readers value was adjusted by the trylock, and so it falls through
bdc76f
+   to waiting on the lock for explicit handover from either a new writer
bdc76f
+   or a new reader.
bdc76f
+
bdc76f
+   448           int err = futex_abstimed_wait (&rwlock->__data.__wrphase_futex,
bdc76f
+   449                                          1 | PTHREAD_RWLOCK_FUTEX_USED,
bdc76f
+   450                                          abstime, private);
bdc76f
+
bdc76f
+   We use PTHREAD_RWLOCK_FUTEX_USED to indicate the futex
bdc76f
+   is in use.
bdc76f
+
bdc76f
+   At this point we have readers waiting on the read lock
bdc76f
+   to unlock. The wrlock is done. The trylock is finishing
bdc76f
+   the installation of the read phase.
bdc76f
+
bdc76f
+    92   if ((r & PTHREAD_RWLOCK_WRPHASE) != 0)
bdc76f
+    93     {
bdc76f
+   ...
bdc76f
+   105       atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 0);
bdc76f
+   106     }
bdc76f
+
bdc76f
+   The trylock does note that we were the one that
bdc76f
+   installed the read phase, but the comments are not
bdc76f
+   correct, the execution ordering above shows that
bdc76f
+   readers might indeed be waiting, and they are.
bdc76f
+
bdc76f
+   The atomic_store_relaxed throws away PTHREAD_RWLOCK_FUTEX_USED,
bdc76f
+   and the waiting reader is never worken becuase as noted
bdc76f
+   above it is conditional on the futex being used.
bdc76f
+
bdc76f
+   The solution is for the trylock thread to inspect
bdc76f
+   PTHREAD_RWLOCK_FUTEX_USED and wake the waiting readers.
bdc76f
+
bdc76f
+   --- Analysis of pthread_rwlock_trywrlock() stall ---
bdc76f
+
bdc76f
+   A write lock begins to execute, takes the write lock,
bdc76f
+   and then releases the lock...
bdc76f
+
bdc76f
+   In pthread_rwlock_wrunlock():
bdc76f
+
bdc76f
+   547   unsigned int r = atomic_load_relaxed (&rwlock->__data.__readers);
bdc76f
+   ...
bdc76f
+   549   while (!atomic_compare_exchange_weak_release
bdc76f
+   550          (&rwlock->__data.__readers, &r,
bdc76f
+   551           ((r ^ PTHREAD_RWLOCK_WRLOCKED)
bdc76f
+   552            ^ ((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0 ? 0
bdc76f
+   553               : PTHREAD_RWLOCK_WRPHASE))))
bdc76f
+   554     {
bdc76f
+   ...
bdc76f
+   556     }
bdc76f
+
bdc76f
+   ... leaving it in the write phase with zero readers
bdc76f
+   (the case where we leave the write phase in place
bdc76f
+   during a write unlock).
bdc76f
+
bdc76f
+   A write trylock begins to execute.
bdc76f
+
bdc76f
+   In __pthread_rwlock_trywrlock:
bdc76f
+
bdc76f
+    40   while (((r & PTHREAD_RWLOCK_WRLOCKED) == 0)
bdc76f
+    41       && (((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0)
bdc76f
+    42           || (prefer_writer && ((r & PTHREAD_RWLOCK_WRPHASE) != 0))))
bdc76f
+    43     {
bdc76f
+
bdc76f
+   The lock is not locked.
bdc76f
+
bdc76f
+   There are no readers.
bdc76f
+
bdc76f
+    45       if (atomic_compare_exchange_weak_acquire (
bdc76f
+    46           &rwlock->__data.__readers, &r,
bdc76f
+    47           r | PTHREAD_RWLOCK_WRPHASE | PTHREAD_RWLOCK_WRLOCKED))
bdc76f
+
bdc76f
+   We atomically install the write phase and we take the
bdc76f
+   exclusive write lock.
bdc76f
+
bdc76f
+    48         {
bdc76f
+    49           atomic_store_relaxed (&rwlock->__data.__writers_futex, 1);
bdc76f
+
bdc76f
+   We get this far.
bdc76f
+
bdc76f
+   A reader lock begins to execute.
bdc76f
+
bdc76f
+   In pthread_rwlock_rdlock:
bdc76f
+
bdc76f
+   437   for (;;)
bdc76f
+   438     {
bdc76f
+   439       while (((wpf = atomic_load_relaxed (&rwlock->__data.__wrphase_futex))
bdc76f
+   440               | PTHREAD_RWLOCK_FUTEX_USED) == (1 | PTHREAD_RWLOCK_FUTEX_USED))
bdc76f
+   441         {
bdc76f
+   442           int private = __pthread_rwlock_get_private (rwlock);
bdc76f
+   443           if (((wpf & PTHREAD_RWLOCK_FUTEX_USED) == 0)
bdc76f
+   444               && (!atomic_compare_exchange_weak_relaxed
bdc76f
+   445                   (&rwlock->__data.__wrphase_futex,
bdc76f
+   446                    &wpf, wpf | PTHREAD_RWLOCK_FUTEX_USED)))
bdc76f
+   447             continue;
bdc76f
+   448           int err = futex_abstimed_wait (&rwlock->__data.__wrphase_futex,
bdc76f
+   449                                          1 | PTHREAD_RWLOCK_FUTEX_USED,
bdc76f
+   450                                          abstime, private);
bdc76f
+
bdc76f
+   We are in a write phase, so the while() on line 439 is true.
bdc76f
+
bdc76f
+   The value of wpf does not have PTHREAD_RWLOCK_FUTEX_USED set
bdc76f
+   since this is the first reader to lock.
bdc76f
+
bdc76f
+   The atomic operation sets wpf with PTHREAD_RELOCK_FUTEX_USED
bdc76f
+   on the expectation that this reader will be woken during
bdc76f
+   the handoff.
bdc76f
+
bdc76f
+   Back in pthread_rwlock_trywrlock:
bdc76f
+
bdc76f
+    50           atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 1);
bdc76f
+    51           atomic_store_relaxed (&rwlock->__data.__cur_writer,
bdc76f
+    52               THREAD_GETMEM (THREAD_SELF, tid));
bdc76f
+    53           return 0;
bdc76f
+    54         }
bdc76f
+   ...
bdc76f
+    57     }
bdc76f
+
bdc76f
+   We write 1 to __wrphase_futex discarding PTHREAD_RWLOCK_FUTEX_USED,
bdc76f
+   and so in the unlock we will not awaken the waiting reader.
bdc76f
+
bdc76f
+   The solution to this is to realize that if we did not start the write
bdc76f
+   phase we need not write 1 or any other value to __wrphase_futex.
bdc76f
+   This ensures that any readers (which saw __wrphase_futex != 0) can
bdc76f
+   set PTHREAD_RWLOCK_FUTEX_USED and this can be used at unlock to
bdc76f
+   wake them.
bdc76f
+
bdc76f
+   If we installed the write phase then all other readers are looping
bdc76f
+   here:
bdc76f
+
bdc76f
+   In __pthread_rwlock_rdlock_full:
bdc76f
+
bdc76f
+   437   for (;;)
bdc76f
+   438     {
bdc76f
+   439       while (((wpf = atomic_load_relaxed (&rwlock->__data.__wrphase_futex))
bdc76f
+   440               | PTHREAD_RWLOCK_FUTEX_USED) == (1 | PTHREAD_RWLOCK_FUTEX_USED))
bdc76f
+   441         {
bdc76f
+   ...
bdc76f
+   508     }
bdc76f
+
bdc76f
+   waiting for the write phase to be installed or removed before they
bdc76f
+   can begin waiting on __wrphase_futex (part of the algorithm), or
bdc76f
+   taking a concurrent read lock, and thus we can safely write 1 to
bdc76f
+   __wrphase_futex.
bdc76f
+
bdc76f
+   If we did not install the write phase then the readers may already
bdc76f
+   be waiting on the futex, the original writer wrote 1 to __wrphase_futex
bdc76f
+   as part of starting the write phase, and we cannot also write 1
bdc76f
+   without loosing the PTHREAD_RWLOCK_FUTEX_USED bit.
bdc76f
+
bdc76f
+   ---
bdc76f
+
bdc76f
+   Summary for the pthread_rwlock_tryrdlock() stall:
bdc76f
+
bdc76f
+   The stall is caused by pthread_rwlock_tryrdlock failing to check
bdc76f
+   that PTHREAD_RWLOCK_FUTEX_USED is set in the __wrphase_futex futex
bdc76f
+   and then waking the futex.
bdc76f
+
bdc76f
+   The fix for bug 23844 ensures that waiters on __wrphase_futex are
bdc76f
+   correctly woken.  Before the fix the test stalls as readers can
bdc76f
+   wait forever on __wrphase_futex.  */
bdc76f
+
bdc76f
+#include <stdio.h>
bdc76f
+#include <stdlib.h>
bdc76f
+#include <unistd.h>
bdc76f
+#include <pthread.h>
bdc76f
+#include <support/xthread.h>
bdc76f
+#include <errno.h>
bdc76f
+
bdc76f
+/* We need only one lock to reproduce the issue. We will need multiple
bdc76f
+   threads to get the exact case where we have a read, try, and unlock
bdc76f
+   all interleaving to produce the case where the readers are waiting
bdc76f
+   and the try fails to wake them.  */
bdc76f
+pthread_rwlock_t onelock;
bdc76f
+
bdc76f
+/* The number of threads is arbitrary but empirically chosen to have
bdc76f
+   enough threads that we see the condition where waiting readers are
bdc76f
+   not woken by a successful tryrdlock.  */
bdc76f
+#define NTHREADS 32
bdc76f
+
bdc76f
+_Atomic int do_exit;
bdc76f
+
bdc76f
+void *
bdc76f
+run_loop (void *arg)
bdc76f
+{
bdc76f
+  int i = 0, ret;
bdc76f
+  while (!do_exit)
bdc76f
+    {
bdc76f
+      /* Arbitrarily choose if we are the writer or reader.  Choose a
bdc76f
+	 high enough ratio of readers to writers to make it likely
bdc76f
+	 that readers block (and eventually are susceptable to
bdc76f
+	 stalling).
bdc76f
+
bdc76f
+         If we are a writer, take the write lock, and then unlock.
bdc76f
+	 If we are a reader, try the lock, then lock, then unlock.  */
bdc76f
+      if ((i % 8) != 0)
bdc76f
+	xpthread_rwlock_wrlock (&onelock);
bdc76f
+      else
bdc76f
+	{
bdc76f
+	  if ((ret = pthread_rwlock_tryrdlock (&onelock)) != 0)
bdc76f
+	    {
bdc76f
+	      if (ret == EBUSY)
bdc76f
+		xpthread_rwlock_rdlock (&onelock);
bdc76f
+	      else
bdc76f
+		exit (EXIT_FAILURE);
bdc76f
+	    }
bdc76f
+	}
bdc76f
+      /* Thread does some work and then unlocks.  */
bdc76f
+      xpthread_rwlock_unlock (&onelock);
bdc76f
+      i++;
bdc76f
+    }
bdc76f
+  return NULL;
bdc76f
+}
bdc76f
+
bdc76f
+int
bdc76f
+do_test (void)
bdc76f
+{
bdc76f
+  int i;
bdc76f
+  pthread_t tids[NTHREADS];
bdc76f
+  xpthread_rwlock_init (&onelock, NULL);
bdc76f
+  for (i = 0; i < NTHREADS; i++)
bdc76f
+    tids[i] = xpthread_create (NULL, run_loop, NULL);
bdc76f
+  /* Run for some amount of time.  Empirically speaking exercising
bdc76f
+     the stall via pthread_rwlock_tryrdlock is much harder, and on
bdc76f
+     a 3.5GHz 4 core x86_64 VM system it takes somewhere around
bdc76f
+     20-200s to stall, approaching 100% stall past 200s.  We can't
bdc76f
+     wait that long for a regression test so we just test for 20s,
bdc76f
+     and expect the stall to happen with a 5-10% chance (enough for
bdc76f
+     developers to see).  */
bdc76f
+  sleep (20);
bdc76f
+  /* Then exit.  */
bdc76f
+  printf ("INFO: Exiting...\n");
bdc76f
+  do_exit = 1;
bdc76f
+  /* If any readers stalled then we will timeout waiting for them.  */
bdc76f
+  for (i = 0; i < NTHREADS; i++)
bdc76f
+    xpthread_join (tids[i]);
bdc76f
+  printf ("INFO: Done.\n");
bdc76f
+  xpthread_rwlock_destroy (&onelock);
bdc76f
+  printf ("PASS: No pthread_rwlock_tryrdlock stalls detected.\n");
bdc76f
+  return 0;
bdc76f
+}
bdc76f
+
bdc76f
+#define TIMEOUT 30
bdc76f
+#include <support/test-driver.c>
bdc76f
diff --git a/nptl/tst-rwlock-trywrlock-stall.c b/nptl/tst-rwlock-trywrlock-stall.c
bdc76f
new file mode 100644
bdc76f
index 0000000000000000..14d27cbcbc882cb1
bdc76f
--- /dev/null
bdc76f
+++ b/nptl/tst-rwlock-trywrlock-stall.c
bdc76f
@@ -0,0 +1,108 @@
bdc76f
+/* Bug 23844: Test for pthread_rwlock_trywrlock stalls.
bdc76f
+   Copyright (C) 2019 Free Software Foundation, Inc.
bdc76f
+   This file is part of the GNU C Library.
bdc76f
+
bdc76f
+   The GNU C Library is free software; you can redistribute it and/or
bdc76f
+   modify it under the terms of the GNU Lesser General Public
bdc76f
+   License as published by the Free Software Foundation; either
bdc76f
+   version 2.1 of the License, or (at your option) any later version.
bdc76f
+
bdc76f
+   The GNU C Library is distributed in the hope that it will be useful,
bdc76f
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
bdc76f
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
bdc76f
+   Lesser General Public License for more details.
bdc76f
+
bdc76f
+   You should have received a copy of the GNU Lesser General Public
bdc76f
+   License along with the GNU C Library; if not, see
bdc76f
+   <http://www.gnu.org/licenses/>.  */
bdc76f
+
bdc76f
+/* For a full analysis see comments in tst-rwlock-tryrdlock-stall.c.
bdc76f
+
bdc76f
+   Summary for the pthread_rwlock_trywrlock() stall:
bdc76f
+
bdc76f
+   The stall is caused by pthread_rwlock_trywrlock setting
bdc76f
+   __wrphase_futex futex to 1 and loosing the
bdc76f
+   PTHREAD_RWLOCK_FUTEX_USED bit.
bdc76f
+
bdc76f
+   The fix for bug 23844 ensures that waiters on __wrphase_futex are
bdc76f
+   correctly woken.  Before the fix the test stalls as readers can
bdc76f
+   wait forever on  __wrphase_futex.  */
bdc76f
+
bdc76f
+#include <stdio.h>
bdc76f
+#include <stdlib.h>
bdc76f
+#include <unistd.h>
bdc76f
+#include <pthread.h>
bdc76f
+#include <support/xthread.h>
bdc76f
+#include <errno.h>
bdc76f
+
bdc76f
+/* We need only one lock to reproduce the issue. We will need multiple
bdc76f
+   threads to get the exact case where we have a read, try, and unlock
bdc76f
+   all interleaving to produce the case where the readers are waiting
bdc76f
+   and the try clears the PTHREAD_RWLOCK_FUTEX_USED bit and a
bdc76f
+   subsequent unlock fails to wake them.  */
bdc76f
+pthread_rwlock_t onelock;
bdc76f
+
bdc76f
+/* The number of threads is arbitrary but empirically chosen to have
bdc76f
+   enough threads that we see the condition where waiting readers are
bdc76f
+   not woken by a successful unlock.  */
bdc76f
+#define NTHREADS 32
bdc76f
+
bdc76f
+_Atomic int do_exit;
bdc76f
+
bdc76f
+void *
bdc76f
+run_loop (void *arg)
bdc76f
+{
bdc76f
+  int i = 0, ret;
bdc76f
+  while (!do_exit)
bdc76f
+    {
bdc76f
+      /* Arbitrarily choose if we are the writer or reader.  Choose a
bdc76f
+	 high enough ratio of readers to writers to make it likely
bdc76f
+	 that readers block (and eventually are susceptable to
bdc76f
+	 stalling).
bdc76f
+
bdc76f
+         If we are a writer, take the write lock, and then unlock.
bdc76f
+	 If we are a reader, try the lock, then lock, then unlock.  */
bdc76f
+      if ((i % 8) != 0)
bdc76f
+	{
bdc76f
+	  if ((ret = pthread_rwlock_trywrlock (&onelock)) != 0)
bdc76f
+	    {
bdc76f
+	      if (ret == EBUSY)
bdc76f
+		xpthread_rwlock_wrlock (&onelock);
bdc76f
+	      else
bdc76f
+		exit (EXIT_FAILURE);
bdc76f
+	    }
bdc76f
+	}
bdc76f
+      else
bdc76f
+	xpthread_rwlock_rdlock (&onelock);
bdc76f
+      /* Thread does some work and then unlocks.  */
bdc76f
+      xpthread_rwlock_unlock (&onelock);
bdc76f
+      i++;
bdc76f
+    }
bdc76f
+  return NULL;
bdc76f
+}
bdc76f
+
bdc76f
+int
bdc76f
+do_test (void)
bdc76f
+{
bdc76f
+  int i;
bdc76f
+  pthread_t tids[NTHREADS];
bdc76f
+  xpthread_rwlock_init (&onelock, NULL);
bdc76f
+  for (i = 0; i < NTHREADS; i++)
bdc76f
+    tids[i] = xpthread_create (NULL, run_loop, NULL);
bdc76f
+  /* Run for some amount of time.  The pthread_rwlock_tryrwlock stall
bdc76f
+     is very easy to trigger and happens in seconds under the test
bdc76f
+     conditions.  */
bdc76f
+  sleep (10);
bdc76f
+  /* Then exit.  */
bdc76f
+  printf ("INFO: Exiting...\n");
bdc76f
+  do_exit = 1;
bdc76f
+  /* If any readers stalled then we will timeout waiting for them.  */
bdc76f
+  for (i = 0; i < NTHREADS; i++)
bdc76f
+    xpthread_join (tids[i]);
bdc76f
+  printf ("INFO: Done.\n");
bdc76f
+  xpthread_rwlock_destroy (&onelock);
bdc76f
+  printf ("PASS: No pthread_rwlock_tryrwlock stalls detected.\n");
bdc76f
+  return 0;
bdc76f
+}
bdc76f
+
bdc76f
+#include <support/test-driver.c>
bdc76f
diff --git a/support/Makefile b/support/Makefile
bdc76f
index 93a514301654132e..41da4abaaa5a645a 100644
bdc76f
--- a/support/Makefile
bdc76f
+++ b/support/Makefile
bdc76f
@@ -129,6 +129,7 @@ libsupport-routines = \
bdc76f
   xpthread_mutexattr_settype \
bdc76f
   xpthread_once \
bdc76f
   xpthread_rwlock_init \
bdc76f
+  xpthread_rwlock_destroy \
bdc76f
   xpthread_rwlock_rdlock \
bdc76f
   xpthread_rwlock_unlock \
bdc76f
   xpthread_rwlock_wrlock \
bdc76f
diff --git a/support/xpthread_rwlock_destroy.c b/support/xpthread_rwlock_destroy.c
bdc76f
new file mode 100644
bdc76f
index 0000000000000000..6d6e95356963b47f
bdc76f
--- /dev/null
bdc76f
+++ b/support/xpthread_rwlock_destroy.c
bdc76f
@@ -0,0 +1,26 @@
bdc76f
+/* pthread_rwlock_destroy with error checking.
bdc76f
+   Copyright (C) 2019 Free Software Foundation, Inc.
bdc76f
+   This file is part of the GNU C Library.
bdc76f
+
bdc76f
+   The GNU C Library is free software; you can redistribute it and/or
bdc76f
+   modify it under the terms of the GNU Lesser General Public
bdc76f
+   License as published by the Free Software Foundation; either
bdc76f
+   version 2.1 of the License, or (at your option) any later version.
bdc76f
+
bdc76f
+   The GNU C Library is distributed in the hope that it will be useful,
bdc76f
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
bdc76f
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
bdc76f
+   Lesser General Public License for more details.
bdc76f
+
bdc76f
+   You should have received a copy of the GNU Lesser General Public
bdc76f
+   License along with the GNU C Library; if not, see
bdc76f
+   <http://www.gnu.org/licenses/>.  */
bdc76f
+
bdc76f
+#include <support/xthread.h>
bdc76f
+
bdc76f
+void
bdc76f
+xpthread_rwlock_destroy (pthread_rwlock_t *rwlock)
bdc76f
+{
bdc76f
+  xpthread_check_return ("pthread_rwlock_destroy",
bdc76f
+                         pthread_rwlock_destroy (rwlock));
bdc76f
+}
bdc76f
diff --git a/support/xthread.h b/support/xthread.h
bdc76f
index 623f5ad0acb895ef..1af77280871464c2 100644
bdc76f
--- a/support/xthread.h
bdc76f
+++ b/support/xthread.h
bdc76f
@@ -84,6 +84,7 @@ void xpthread_rwlockattr_setkind_np (pthread_rwlockattr_t *attr, int pref);
bdc76f
 void xpthread_rwlock_wrlock (pthread_rwlock_t *rwlock);
bdc76f
 void xpthread_rwlock_rdlock (pthread_rwlock_t *rwlock);
bdc76f
 void xpthread_rwlock_unlock (pthread_rwlock_t *rwlock);
bdc76f
+void xpthread_rwlock_destroy (pthread_rwlock_t *rwlock);
bdc76f
 
bdc76f
 __END_DECLS
bdc76f