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