Blame nptl/pthread_rwlock_tryrdlock.c

Packit 6c4009
/* Copyright (C) 2002-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
   Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include "pthreadP.h"
Packit 6c4009
#include <atomic.h>
Packit 6c4009
#include <stdbool.h>
Packit 6c4009
#include "pthread_rwlock_common.c"
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* See pthread_rwlock_common.c for an overview.  */
Packit 6c4009
int
Packit 6c4009
__pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock)
Packit 6c4009
{
Packit 6c4009
  /* For tryrdlock, we could speculate that we will succeed and go ahead and
Packit 6c4009
     register as a reader.  However, if we misspeculate, we have to do the
Packit 6c4009
     same steps as a timed-out rdlock, which will increase contention.
Packit 6c4009
     Therefore, there is a trade-off between being able to use a combinable
Packit 6c4009
     read-modify-write operation and a CAS loop as used below; we pick the
Packit 6c4009
     latter because it simplifies the code, and should perform better when
Packit 6c4009
     tryrdlock is used in cases where writers are infrequent.
Packit 6c4009
     Because POSIX does not require a failed trylock to "synchronize memory",
Packit 6c4009
     relaxed MO is sufficient here and on the failure path of the CAS
Packit 6c4009
     below.  */
Packit 6c4009
  unsigned int r = atomic_load_relaxed (&rwlock->__data.__readers);
Packit 6c4009
  unsigned int rnew;
Packit 6c4009
  do
Packit 6c4009
    {
Packit 6c4009
      if ((r & PTHREAD_RWLOCK_WRPHASE) == 0)
Packit 6c4009
	{
Packit 6c4009
	  /* If we are in a read phase, try to acquire unless there is a
Packit 6c4009
	     primary writer and we prefer writers and there will be no
Packit 6c4009
	     recursive read locks.  */
Packit 6c4009
	  if (((r & PTHREAD_RWLOCK_WRLOCKED) != 0)
Packit 6c4009
	      && (rwlock->__data.__flags
Packit 6c4009
		  == PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))
Packit 6c4009
	    return EBUSY;
Packit 6c4009
	  rnew = r + (1 << PTHREAD_RWLOCK_READER_SHIFT);
Packit 6c4009
	}
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  /* If there is a writer that has acquired the lock and we are in
Packit 6c4009
	     a write phase, fail.  */
Packit 6c4009
	  if ((r & PTHREAD_RWLOCK_WRLOCKED) != 0)
Packit 6c4009
	    return EBUSY;
Packit 6c4009
	  else
Packit 6c4009
	    {
Packit 6c4009
	      /* If we do not care about potentially waiting writers, just
Packit 6c4009
		 try to acquire.  */
Packit 6c4009
	      rnew = (r + (1 << PTHREAD_RWLOCK_READER_SHIFT))
Packit 6c4009
		  ^ PTHREAD_RWLOCK_WRPHASE;
Packit 6c4009
	    }
Packit 6c4009
	}
Packit 6c4009
      /* If we could have caused an overflow or take effect during an
Packit 6c4009
	 overflow, we just can / need to return EAGAIN.  There is no need to
Packit 6c4009
	 have actually modified the number of readers because we could have
Packit 6c4009
	 done that and cleaned up immediately.  */
Packit 6c4009
      if (rnew >= PTHREAD_RWLOCK_READER_OVERFLOW)
Packit 6c4009
	return EAGAIN;
Packit 6c4009
    }
Packit 6c4009
  /* If the CAS fails, we retry; this prevents that tryrdlock fails spuriously
Packit 6c4009
     (i.e., fails to acquire the lock although there is no writer), which is
Packit 6c4009
     fine for C++14 but not currently allowed by POSIX.
Packit 6c4009
     However, because tryrdlock must not appear to block, we should avoid
Packit 6c4009
     starving this CAS loop due to constant changes to __readers:
Packit 6c4009
     While normal rdlock readers that won't be able to acquire will just block
Packit 6c4009
     (and we expect timeouts on timedrdlock to be longer than one retry of the
Packit 6c4009
     CAS loop), we can have concurrently failing tryrdlock calls due to
Packit 6c4009
     readers or writers that acquire and release in the meantime.  Using
Packit 6c4009
     randomized exponential back-off to make a live-lock unlikely should be
Packit 6c4009
     sufficient.
Packit 6c4009
     TODO Back-off.
Packit 6c4009
     Acquire MO so we synchronize with prior writers.  */
Packit 6c4009
  while (!atomic_compare_exchange_weak_acquire (&rwlock->__data.__readers,
Packit 6c4009
      &r, rnew));
Packit 6c4009
Packit 6c4009
  if ((r & PTHREAD_RWLOCK_WRPHASE) != 0)
Packit 6c4009
    {
Packit 6c4009
      /* Same as in __pthread_rwlock_rdlock_full:
Packit 6c4009
	 We started the read phase, so we are also responsible for
Packit 6c4009
	 updating the write-phase futex.  Relaxed MO is sufficient.
Packit Service fc2dbd
	 We have to do the same steps as a writer would when handing over the
Packit Service fc2dbd
	 read phase to use because other readers cannot distinguish between
Packit Service fc2dbd
	 us and the writer.
Packit Service fc2dbd
	 Note that __pthread_rwlock_tryrdlock callers will not have to be
Packit Service fc2dbd
	 woken up because they will either see the read phase started by us
Packit Service fc2dbd
	 or they will try to start it themselves; however, callers of
Packit Service fc2dbd
	 __pthread_rwlock_rdlock_full just increase the reader count and then
Packit Service fc2dbd
	 check what state the lock is in, so they cannot distinguish between
Packit Service fc2dbd
	 us and a writer that acquired and released the lock in the
Packit Service fc2dbd
	 meantime.  */
Packit Service fc2dbd
      if ((atomic_exchange_relaxed (&rwlock->__data.__wrphase_futex, 0)
Packit Service fc2dbd
	  & PTHREAD_RWLOCK_FUTEX_USED) != 0)
Packit Service fc2dbd
	{
Packit Service fc2dbd
	  int private = __pthread_rwlock_get_private (rwlock);
Packit Service fc2dbd
	  futex_wake (&rwlock->__data.__wrphase_futex, INT_MAX, private);
Packit Service fc2dbd
	}
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  return 0;
Packit 6c4009
Packit 6c4009
Packit 6c4009
}
Packit 6c4009
strong_alias (__pthread_rwlock_tryrdlock, pthread_rwlock_tryrdlock)