Blame nptl/tst-rwlock-tryrdlock-stall.c

Packit Service 2c6cac
/* Bug 23844: Test for pthread_rwlock_tryrdlock stalls.
Packit Service 2c6cac
   Copyright (C) 2019 Free Software Foundation, Inc.
Packit Service 2c6cac
   This file is part of the GNU C Library.
Packit Service 2c6cac
Packit Service 2c6cac
   The GNU C Library is free software; you can redistribute it and/or
Packit Service 2c6cac
   modify it under the terms of the GNU Lesser General Public
Packit Service 2c6cac
   License as published by the Free Software Foundation; either
Packit Service 2c6cac
   version 2.1 of the License, or (at your option) any later version.
Packit Service 2c6cac
Packit Service 2c6cac
   The GNU C Library is distributed in the hope that it will be useful,
Packit Service 2c6cac
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 2c6cac
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit Service 2c6cac
   Lesser General Public License for more details.
Packit Service 2c6cac
Packit Service 2c6cac
   You should have received a copy of the GNU Lesser General Public
Packit Service 2c6cac
   License along with the GNU C Library; if not, see
Packit Service 2c6cac
   <http://www.gnu.org/licenses/>.  */
Packit Service 2c6cac
Packit Service 2c6cac
/* For a full analysis see comment:
Packit Service 2c6cac
   https://sourceware.org/bugzilla/show_bug.cgi?id=23844#c14
Packit Service 2c6cac
Packit Service 2c6cac
   Provided here for reference:
Packit Service 2c6cac
Packit Service 2c6cac
   --- Analysis of pthread_rwlock_tryrdlock() stall ---
Packit Service 2c6cac
   A read lock begins to execute.
Packit Service 2c6cac
Packit Service 2c6cac
   In __pthread_rwlock_rdlock_full:
Packit Service 2c6cac
Packit Service 2c6cac
   We can attempt a read lock, but find that the lock is
Packit Service 2c6cac
   in a write phase (PTHREAD_RWLOCK_WRPHASE, or WP-bit
Packit Service 2c6cac
   is set), and the lock is held by a primary writer
Packit Service 2c6cac
   (PTHREAD_RWLOCK_WRLOCKED is set). In this case we must
Packit Service 2c6cac
   wait for explicit hand over from the writer to us or
Packit Service 2c6cac
   one of the other waiters. The read lock threads are
Packit Service 2c6cac
   about to execute:
Packit Service 2c6cac
Packit Service 2c6cac
   341   r = (atomic_fetch_add_acquire (&rwlock->__data.__readers,
Packit Service 2c6cac
   342                                  (1 << PTHREAD_RWLOCK_READER_SHIFT))
Packit Service 2c6cac
   343        + (1 << PTHREAD_RWLOCK_READER_SHIFT));
Packit Service 2c6cac
Packit Service 2c6cac
   An unlock beings to execute.
Packit Service 2c6cac
Packit Service 2c6cac
   Then in __pthread_rwlock_wrunlock:
Packit Service 2c6cac
Packit Service 2c6cac
   547   unsigned int r = atomic_load_relaxed (&rwlock->__data.__readers);
Packit Service 2c6cac
   ...
Packit Service 2c6cac
   549   while (!atomic_compare_exchange_weak_release
Packit Service 2c6cac
   550          (&rwlock->__data.__readers, &r,
Packit Service 2c6cac
   551           ((r ^ PTHREAD_RWLOCK_WRLOCKED)
Packit Service 2c6cac
   552            ^ ((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0 ? 0
Packit Service 2c6cac
   553               : PTHREAD_RWLOCK_WRPHASE))))
Packit Service 2c6cac
   554     {
Packit Service 2c6cac
   ...
Packit Service 2c6cac
   556     }
Packit Service 2c6cac
Packit Service 2c6cac
   We clear PTHREAD_RWLOCK_WRLOCKED, and if there are
Packit Service 2c6cac
   no readers so we leave the lock in PTHRAD_RWLOCK_WRPHASE.
Packit Service 2c6cac
Packit Service 2c6cac
   Back in the read lock.
Packit Service 2c6cac
Packit Service 2c6cac
   The read lock adjusts __readres as above.
Packit Service 2c6cac
Packit Service 2c6cac
   383   while ((r & PTHREAD_RWLOCK_WRPHASE) != 0
Packit Service 2c6cac
   384          && (r & PTHREAD_RWLOCK_WRLOCKED) == 0)
Packit Service 2c6cac
   385     {
Packit Service 2c6cac
   ...
Packit Service 2c6cac
   390       if (atomic_compare_exchange_weak_acquire (&rwlock->__data.__readers, &r,
Packit Service 2c6cac
   391                                                 r ^ PTHREAD_RWLOCK_WRPHASE))
Packit Service 2c6cac
   392         {
Packit Service 2c6cac
Packit Service 2c6cac
   And then attemps to start the read phase.
Packit Service 2c6cac
Packit Service 2c6cac
   Assume there happens to be a tryrdlock at this point, noting
Packit Service 2c6cac
   that PTHREAD_RWLOCK_WRLOCKED is clear, and PTHREAD_RWLOCK_WRPHASE
Packit Service 2c6cac
   is 1. So the try lock attemps to start the read phase.
Packit Service 2c6cac
Packit Service 2c6cac
   In __pthread_rwlock_tryrdlock:
Packit Service 2c6cac
Packit Service 2c6cac
    44       if ((r & PTHREAD_RWLOCK_WRPHASE) == 0)
Packit Service 2c6cac
    45         {
Packit Service 2c6cac
   ...
Packit Service 2c6cac
    49           if (((r & PTHREAD_RWLOCK_WRLOCKED) != 0)
Packit Service 2c6cac
    50               && (rwlock->__data.__flags
Packit Service 2c6cac
    51                   == PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))
Packit Service 2c6cac
    52             return EBUSY;
Packit Service 2c6cac
    53           rnew = r + (1 << PTHREAD_RWLOCK_READER_SHIFT);
Packit Service 2c6cac
    54         }
Packit Service 2c6cac
   ...
Packit Service 2c6cac
    89   while (!atomic_compare_exchange_weak_acquire (&rwlock->__data.__readers,
Packit Service 2c6cac
    90       &r, rnew));
Packit Service 2c6cac
Packit Service 2c6cac
   And succeeds.
Packit Service 2c6cac
Packit Service 2c6cac
   Back in the write unlock:
Packit Service 2c6cac
Packit Service 2c6cac
   557   if ((r >> PTHREAD_RWLOCK_READER_SHIFT) != 0)
Packit Service 2c6cac
   558     {
Packit Service 2c6cac
   ...
Packit Service 2c6cac
   563       if ((atomic_exchange_relaxed (&rwlock->__data.__wrphase_futex, 0)
Packit Service 2c6cac
   564            & PTHREAD_RWLOCK_FUTEX_USED) != 0)
Packit Service 2c6cac
   565         futex_wake (&rwlock->__data.__wrphase_futex, INT_MAX, private);
Packit Service 2c6cac
   566     }
Packit Service 2c6cac
Packit Service 2c6cac
   We note that PTHREAD_RWLOCK_FUTEX_USED is non-zero
Packit Service 2c6cac
   and don't wake anyone. This is OK because we handed
Packit Service 2c6cac
   over to the trylock. It will be the trylock's responsibility
Packit Service 2c6cac
   to wake any waiters.
Packit Service 2c6cac
Packit Service 2c6cac
   Back in the read lock:
Packit Service 2c6cac
Packit Service 2c6cac
   The read lock fails to install PTHRAD_REWLOCK_WRPHASE as 0 because
Packit Service 2c6cac
   the __readers value was adjusted by the trylock, and so it falls through
Packit Service 2c6cac
   to waiting on the lock for explicit handover from either a new writer
Packit Service 2c6cac
   or a new reader.
Packit Service 2c6cac
Packit Service 2c6cac
   448           int err = futex_abstimed_wait (&rwlock->__data.__wrphase_futex,
Packit Service 2c6cac
   449                                          1 | PTHREAD_RWLOCK_FUTEX_USED,
Packit Service 2c6cac
   450                                          abstime, private);
Packit Service 2c6cac
Packit Service 2c6cac
   We use PTHREAD_RWLOCK_FUTEX_USED to indicate the futex
Packit Service 2c6cac
   is in use.
Packit Service 2c6cac
Packit Service 2c6cac
   At this point we have readers waiting on the read lock
Packit Service 2c6cac
   to unlock. The wrlock is done. The trylock is finishing
Packit Service 2c6cac
   the installation of the read phase.
Packit Service 2c6cac
Packit Service 2c6cac
    92   if ((r & PTHREAD_RWLOCK_WRPHASE) != 0)
Packit Service 2c6cac
    93     {
Packit Service 2c6cac
   ...
Packit Service 2c6cac
   105       atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 0);
Packit Service 2c6cac
   106     }
Packit Service 2c6cac
Packit Service 2c6cac
   The trylock does note that we were the one that
Packit Service 2c6cac
   installed the read phase, but the comments are not
Packit Service 2c6cac
   correct, the execution ordering above shows that
Packit Service 2c6cac
   readers might indeed be waiting, and they are.
Packit Service 2c6cac
Packit Service 2c6cac
   The atomic_store_relaxed throws away PTHREAD_RWLOCK_FUTEX_USED,
Packit Service 2c6cac
   and the waiting reader is never worken becuase as noted
Packit Service 2c6cac
   above it is conditional on the futex being used.
Packit Service 2c6cac
Packit Service 2c6cac
   The solution is for the trylock thread to inspect
Packit Service 2c6cac
   PTHREAD_RWLOCK_FUTEX_USED and wake the waiting readers.
Packit Service 2c6cac
Packit Service 2c6cac
   --- Analysis of pthread_rwlock_trywrlock() stall ---
Packit Service 2c6cac
Packit Service 2c6cac
   A write lock begins to execute, takes the write lock,
Packit Service 2c6cac
   and then releases the lock...
Packit Service 2c6cac
Packit Service 2c6cac
   In pthread_rwlock_wrunlock():
Packit Service 2c6cac
Packit Service 2c6cac
   547   unsigned int r = atomic_load_relaxed (&rwlock->__data.__readers);
Packit Service 2c6cac
   ...
Packit Service 2c6cac
   549   while (!atomic_compare_exchange_weak_release
Packit Service 2c6cac
   550          (&rwlock->__data.__readers, &r,
Packit Service 2c6cac
   551           ((r ^ PTHREAD_RWLOCK_WRLOCKED)
Packit Service 2c6cac
   552            ^ ((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0 ? 0
Packit Service 2c6cac
   553               : PTHREAD_RWLOCK_WRPHASE))))
Packit Service 2c6cac
   554     {
Packit Service 2c6cac
   ...
Packit Service 2c6cac
   556     }
Packit Service 2c6cac
Packit Service 2c6cac
   ... leaving it in the write phase with zero readers
Packit Service 2c6cac
   (the case where we leave the write phase in place
Packit Service 2c6cac
   during a write unlock).
Packit Service 2c6cac
Packit Service 2c6cac
   A write trylock begins to execute.
Packit Service 2c6cac
Packit Service 2c6cac
   In __pthread_rwlock_trywrlock:
Packit Service 2c6cac
Packit Service 2c6cac
    40   while (((r & PTHREAD_RWLOCK_WRLOCKED) == 0)
Packit Service 2c6cac
    41       && (((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0)
Packit Service 2c6cac
    42           || (prefer_writer && ((r & PTHREAD_RWLOCK_WRPHASE) != 0))))
Packit Service 2c6cac
    43     {
Packit Service 2c6cac
Packit Service 2c6cac
   The lock is not locked.
Packit Service 2c6cac
Packit Service 2c6cac
   There are no readers.
Packit Service 2c6cac
Packit Service 2c6cac
    45       if (atomic_compare_exchange_weak_acquire (
Packit Service 2c6cac
    46           &rwlock->__data.__readers, &r,
Packit Service 2c6cac
    47           r | PTHREAD_RWLOCK_WRPHASE | PTHREAD_RWLOCK_WRLOCKED))
Packit Service 2c6cac
Packit Service 2c6cac
   We atomically install the write phase and we take the
Packit Service 2c6cac
   exclusive write lock.
Packit Service 2c6cac
Packit Service 2c6cac
    48         {
Packit Service 2c6cac
    49           atomic_store_relaxed (&rwlock->__data.__writers_futex, 1);
Packit Service 2c6cac
Packit Service 2c6cac
   We get this far.
Packit Service 2c6cac
Packit Service 2c6cac
   A reader lock begins to execute.
Packit Service 2c6cac
Packit Service 2c6cac
   In pthread_rwlock_rdlock:
Packit Service 2c6cac
Packit Service 2c6cac
   437   for (;;)
Packit Service 2c6cac
   438     {
Packit Service 2c6cac
   439       while (((wpf = atomic_load_relaxed (&rwlock->__data.__wrphase_futex))
Packit Service 2c6cac
   440               | PTHREAD_RWLOCK_FUTEX_USED) == (1 | PTHREAD_RWLOCK_FUTEX_USED))
Packit Service 2c6cac
   441         {
Packit Service 2c6cac
   442           int private = __pthread_rwlock_get_private (rwlock);
Packit Service 2c6cac
   443           if (((wpf & PTHREAD_RWLOCK_FUTEX_USED) == 0)
Packit Service 2c6cac
   444               && (!atomic_compare_exchange_weak_relaxed
Packit Service 2c6cac
   445                   (&rwlock->__data.__wrphase_futex,
Packit Service 2c6cac
   446                    &wpf, wpf | PTHREAD_RWLOCK_FUTEX_USED)))
Packit Service 2c6cac
   447             continue;
Packit Service 2c6cac
   448           int err = futex_abstimed_wait (&rwlock->__data.__wrphase_futex,
Packit Service 2c6cac
   449                                          1 | PTHREAD_RWLOCK_FUTEX_USED,
Packit Service 2c6cac
   450                                          abstime, private);
Packit Service 2c6cac
Packit Service 2c6cac
   We are in a write phase, so the while() on line 439 is true.
Packit Service 2c6cac
Packit Service 2c6cac
   The value of wpf does not have PTHREAD_RWLOCK_FUTEX_USED set
Packit Service 2c6cac
   since this is the first reader to lock.
Packit Service 2c6cac
Packit Service 2c6cac
   The atomic operation sets wpf with PTHREAD_RELOCK_FUTEX_USED
Packit Service 2c6cac
   on the expectation that this reader will be woken during
Packit Service 2c6cac
   the handoff.
Packit Service 2c6cac
Packit Service 2c6cac
   Back in pthread_rwlock_trywrlock:
Packit Service 2c6cac
Packit Service 2c6cac
    50           atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 1);
Packit Service 2c6cac
    51           atomic_store_relaxed (&rwlock->__data.__cur_writer,
Packit Service 2c6cac
    52               THREAD_GETMEM (THREAD_SELF, tid));
Packit Service 2c6cac
    53           return 0;
Packit Service 2c6cac
    54         }
Packit Service 2c6cac
   ...
Packit Service 2c6cac
    57     }
Packit Service 2c6cac
Packit Service 2c6cac
   We write 1 to __wrphase_futex discarding PTHREAD_RWLOCK_FUTEX_USED,
Packit Service 2c6cac
   and so in the unlock we will not awaken the waiting reader.
Packit Service 2c6cac
Packit Service 2c6cac
   The solution to this is to realize that if we did not start the write
Packit Service 2c6cac
   phase we need not write 1 or any other value to __wrphase_futex.
Packit Service 2c6cac
   This ensures that any readers (which saw __wrphase_futex != 0) can
Packit Service 2c6cac
   set PTHREAD_RWLOCK_FUTEX_USED and this can be used at unlock to
Packit Service 2c6cac
   wake them.
Packit Service 2c6cac
Packit Service 2c6cac
   If we installed the write phase then all other readers are looping
Packit Service 2c6cac
   here:
Packit Service 2c6cac
Packit Service 2c6cac
   In __pthread_rwlock_rdlock_full:
Packit Service 2c6cac
Packit Service 2c6cac
   437   for (;;)
Packit Service 2c6cac
   438     {
Packit Service 2c6cac
   439       while (((wpf = atomic_load_relaxed (&rwlock->__data.__wrphase_futex))
Packit Service 2c6cac
   440               | PTHREAD_RWLOCK_FUTEX_USED) == (1 | PTHREAD_RWLOCK_FUTEX_USED))
Packit Service 2c6cac
   441         {
Packit Service 2c6cac
   ...
Packit Service 2c6cac
   508     }
Packit Service 2c6cac
Packit Service 2c6cac
   waiting for the write phase to be installed or removed before they
Packit Service 2c6cac
   can begin waiting on __wrphase_futex (part of the algorithm), or
Packit Service 2c6cac
   taking a concurrent read lock, and thus we can safely write 1 to
Packit Service 2c6cac
   __wrphase_futex.
Packit Service 2c6cac
Packit Service 2c6cac
   If we did not install the write phase then the readers may already
Packit Service 2c6cac
   be waiting on the futex, the original writer wrote 1 to __wrphase_futex
Packit Service 2c6cac
   as part of starting the write phase, and we cannot also write 1
Packit Service 2c6cac
   without loosing the PTHREAD_RWLOCK_FUTEX_USED bit.
Packit Service 2c6cac
Packit Service 2c6cac
   ---
Packit Service 2c6cac
Packit Service 2c6cac
   Summary for the pthread_rwlock_tryrdlock() stall:
Packit Service 2c6cac
Packit Service 2c6cac
   The stall is caused by pthread_rwlock_tryrdlock failing to check
Packit Service 2c6cac
   that PTHREAD_RWLOCK_FUTEX_USED is set in the __wrphase_futex futex
Packit Service 2c6cac
   and then waking the futex.
Packit Service 2c6cac
Packit Service 2c6cac
   The fix for bug 23844 ensures that waiters on __wrphase_futex are
Packit Service 2c6cac
   correctly woken.  Before the fix the test stalls as readers can
Packit Service 2c6cac
   wait forever on __wrphase_futex.  */
Packit Service 2c6cac
Packit Service 2c6cac
#include <stdio.h>
Packit Service 2c6cac
#include <stdlib.h>
Packit Service 2c6cac
#include <unistd.h>
Packit Service 2c6cac
#include <pthread.h>
Packit Service 2c6cac
#include <support/xthread.h>
Packit Service 2c6cac
#include <errno.h>
Packit Service 2c6cac
Packit Service 2c6cac
/* We need only one lock to reproduce the issue. We will need multiple
Packit Service 2c6cac
   threads to get the exact case where we have a read, try, and unlock
Packit Service 2c6cac
   all interleaving to produce the case where the readers are waiting
Packit Service 2c6cac
   and the try fails to wake them.  */
Packit Service 2c6cac
pthread_rwlock_t onelock;
Packit Service 2c6cac
Packit Service 2c6cac
/* The number of threads is arbitrary but empirically chosen to have
Packit Service 2c6cac
   enough threads that we see the condition where waiting readers are
Packit Service 2c6cac
   not woken by a successful tryrdlock.  */
Packit Service 2c6cac
#define NTHREADS 32
Packit Service 2c6cac
Packit Service 2c6cac
_Atomic int do_exit;
Packit Service 2c6cac
Packit Service 2c6cac
void *
Packit Service 2c6cac
run_loop (void *arg)
Packit Service 2c6cac
{
Packit Service 2c6cac
  int i = 0, ret;
Packit Service 2c6cac
  while (!do_exit)
Packit Service 2c6cac
    {
Packit Service 2c6cac
      /* Arbitrarily choose if we are the writer or reader.  Choose a
Packit Service 2c6cac
	 high enough ratio of readers to writers to make it likely
Packit Service 2c6cac
	 that readers block (and eventually are susceptable to
Packit Service 2c6cac
	 stalling).
Packit Service 2c6cac
Packit Service 2c6cac
         If we are a writer, take the write lock, and then unlock.
Packit Service 2c6cac
	 If we are a reader, try the lock, then lock, then unlock.  */
Packit Service 2c6cac
      if ((i % 8) != 0)
Packit Service 2c6cac
	xpthread_rwlock_wrlock (&onelock);
Packit Service 2c6cac
      else
Packit Service 2c6cac
	{
Packit Service 2c6cac
	  if ((ret = pthread_rwlock_tryrdlock (&onelock)) != 0)
Packit Service 2c6cac
	    {
Packit Service 2c6cac
	      if (ret == EBUSY)
Packit Service 2c6cac
		xpthread_rwlock_rdlock (&onelock);
Packit Service 2c6cac
	      else
Packit Service 2c6cac
		exit (EXIT_FAILURE);
Packit Service 2c6cac
	    }
Packit Service 2c6cac
	}
Packit Service 2c6cac
      /* Thread does some work and then unlocks.  */
Packit Service 2c6cac
      xpthread_rwlock_unlock (&onelock);
Packit Service 2c6cac
      i++;
Packit Service 2c6cac
    }
Packit Service 2c6cac
  return NULL;
Packit Service 2c6cac
}
Packit Service 2c6cac
Packit Service 2c6cac
int
Packit Service 2c6cac
do_test (void)
Packit Service 2c6cac
{
Packit Service 2c6cac
  int i;
Packit Service 2c6cac
  pthread_t tids[NTHREADS];
Packit Service 2c6cac
  xpthread_rwlock_init (&onelock, NULL);
Packit Service 2c6cac
  for (i = 0; i < NTHREADS; i++)
Packit Service 2c6cac
    tids[i] = xpthread_create (NULL, run_loop, NULL);
Packit Service 2c6cac
  /* Run for some amount of time.  Empirically speaking exercising
Packit Service 2c6cac
     the stall via pthread_rwlock_tryrdlock is much harder, and on
Packit Service 2c6cac
     a 3.5GHz 4 core x86_64 VM system it takes somewhere around
Packit Service 2c6cac
     20-200s to stall, approaching 100% stall past 200s.  We can't
Packit Service 2c6cac
     wait that long for a regression test so we just test for 20s,
Packit Service 2c6cac
     and expect the stall to happen with a 5-10% chance (enough for
Packit Service 2c6cac
     developers to see).  */
Packit Service 2c6cac
  sleep (20);
Packit Service 2c6cac
  /* Then exit.  */
Packit Service 2c6cac
  printf ("INFO: Exiting...\n");
Packit Service 2c6cac
  do_exit = 1;
Packit Service 2c6cac
  /* If any readers stalled then we will timeout waiting for them.  */
Packit Service 2c6cac
  for (i = 0; i < NTHREADS; i++)
Packit Service 2c6cac
    xpthread_join (tids[i]);
Packit Service 2c6cac
  printf ("INFO: Done.\n");
Packit Service 2c6cac
  xpthread_rwlock_destroy (&onelock);
Packit Service 2c6cac
  printf ("PASS: No pthread_rwlock_tryrdlock stalls detected.\n");
Packit Service 2c6cac
  return 0;
Packit Service 2c6cac
}
Packit Service 2c6cac
Packit Service 2c6cac
#define TIMEOUT 30
Packit Service 2c6cac
#include <support/test-driver.c>