hjl / source-git / glibc

Forked from source-git/glibc 3 years ago
Clone

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

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