Blame sysdeps/htl/pt-cond-timedwait.c

Packit 6c4009
/* Wait on a condition.  Generic version.
Packit 6c4009
   Copyright (C) 2000-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
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 <pthread.h>
Packit 6c4009
Packit 6c4009
#include <pt-internal.h>
Packit 6c4009
#include <pthreadP.h>
Packit 6c4009
Packit 6c4009
extern int __pthread_cond_timedwait_internal (pthread_cond_t *cond,
Packit 6c4009
					      pthread_mutex_t *mutex,
Packit 6c4009
					      const struct timespec *abstime);
Packit 6c4009
Packit 6c4009
int
Packit 6c4009
__pthread_cond_timedwait (pthread_cond_t *cond,
Packit 6c4009
			  pthread_mutex_t *mutex,
Packit 6c4009
			  const struct timespec *abstime)
Packit 6c4009
{
Packit 6c4009
  return __pthread_cond_timedwait_internal (cond, mutex, abstime);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
strong_alias (__pthread_cond_timedwait, pthread_cond_timedwait);
Packit 6c4009
Packit 6c4009
struct cancel_ctx
Packit 6c4009
{
Packit 6c4009
  struct __pthread *wakeup;
Packit 6c4009
  pthread_cond_t *cond;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
cancel_hook (void *arg)
Packit 6c4009
{
Packit 6c4009
  struct cancel_ctx *ctx = arg;
Packit 6c4009
  struct __pthread *wakeup = ctx->wakeup;
Packit 6c4009
  pthread_cond_t *cond = ctx->cond;
Packit 6c4009
  int unblock;
Packit 6c4009
Packit 6c4009
  __pthread_spin_lock (&cond->__lock);
Packit 6c4009
  /* The thread only needs to be awaken if it's blocking or about to block.
Packit 6c4009
     If it was already unblocked, it's not queued any more.  */
Packit 6c4009
  unblock = wakeup->prevp != NULL;
Packit 6c4009
  if (unblock)
Packit 6c4009
    __pthread_dequeue (wakeup);
Packit 6c4009
  __pthread_spin_unlock (&cond->__lock);
Packit 6c4009
Packit 6c4009
  if (unblock)
Packit 6c4009
    __pthread_wakeup (wakeup);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Block on condition variable COND until ABSTIME.  As a GNU
Packit 6c4009
   extension, if ABSTIME is NULL, then wait forever.  MUTEX should be
Packit 6c4009
   held by the calling thread.  On return, MUTEX will be held by the
Packit 6c4009
   calling thread.  */
Packit 6c4009
int
Packit 6c4009
__pthread_cond_timedwait_internal (pthread_cond_t *cond,
Packit 6c4009
				   pthread_mutex_t *mutex,
Packit 6c4009
				   const struct timespec *abstime)
Packit 6c4009
{
Packit 6c4009
  error_t err;
Packit 6c4009
  int cancelled, oldtype, drain;
Packit 6c4009
  clockid_t clock_id = __pthread_default_condattr.__clock;
Packit 6c4009
Packit 6c4009
  if (abstime && (abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000))
Packit 6c4009
    return EINVAL;
Packit 6c4009
Packit 6c4009
  struct __pthread *self = _pthread_self ();
Packit 6c4009
  struct cancel_ctx ctx;
Packit 6c4009
  ctx.wakeup = self;
Packit 6c4009
  ctx.cond = cond;
Packit 6c4009
Packit 6c4009
  /* Test for a pending cancellation request, switch to deferred mode for
Packit 6c4009
     safer resource handling, and prepare the hook to call in case we're
Packit 6c4009
     cancelled while blocking.  Once CANCEL_LOCK is released, the cancellation
Packit 6c4009
     hook can be called by another thread at any time.  Whatever happens,
Packit 6c4009
     this function must exit with MUTEX locked.
Packit 6c4009
Packit 6c4009
     This function contains inline implementations of pthread_testcancel and
Packit 6c4009
     pthread_setcanceltype to reduce locking overhead.  */
Packit 6c4009
  __pthread_mutex_lock (&self->cancel_lock);
Packit 6c4009
  cancelled = (self->cancel_state == PTHREAD_CANCEL_ENABLE)
Packit 6c4009
      && self->cancel_pending;
Packit 6c4009
Packit 6c4009
  if (!cancelled)
Packit 6c4009
    {
Packit 6c4009
      self->cancel_hook = cancel_hook;
Packit 6c4009
      self->cancel_hook_arg = &ctx;
Packit 6c4009
      oldtype = self->cancel_type;
Packit 6c4009
Packit 6c4009
      if (oldtype != PTHREAD_CANCEL_DEFERRED)
Packit 6c4009
	self->cancel_type = PTHREAD_CANCEL_DEFERRED;
Packit 6c4009
Packit 6c4009
      /* Add ourselves to the list of waiters.  This is done while setting
Packit 6c4009
         the cancellation hook to simplify the cancellation procedure, i.e.
Packit 6c4009
         if the thread is queued, it can be cancelled, otherwise it is
Packit 6c4009
         already unblocked, progressing on the return path.  */
Packit 6c4009
      __pthread_spin_lock (&cond->__lock);
Packit 6c4009
      __pthread_enqueue (&cond->__queue, self);
Packit 6c4009
      if (cond->__attr != NULL)
Packit 6c4009
	clock_id = cond->__attr->__clock;
Packit 6c4009
      __pthread_spin_unlock (&cond->__lock);
Packit 6c4009
    }
Packit 6c4009
  __pthread_mutex_unlock (&self->cancel_lock);
Packit 6c4009
Packit 6c4009
  if (cancelled)
Packit 6c4009
    __pthread_exit (PTHREAD_CANCELED);
Packit 6c4009
Packit 6c4009
  /* Release MUTEX before blocking.  */
Packit 6c4009
  __pthread_mutex_unlock (mutex);
Packit 6c4009
Packit 6c4009
  /* Block the thread.  */
Packit 6c4009
  if (abstime != NULL)
Packit 6c4009
    err = __pthread_timedblock (self, abstime, clock_id);
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      err = 0;
Packit 6c4009
      __pthread_block (self);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  __pthread_spin_lock (&cond->__lock);
Packit 6c4009
  if (self->prevp == NULL)
Packit 6c4009
    {
Packit 6c4009
      /* Another thread removed us from the list of waiters, which means a
Packit 6c4009
         wakeup message has been sent.  It was either consumed while we were
Packit 6c4009
         blocking, or queued after we timed out and before we acquired the
Packit 6c4009
         condition lock, in which case the message queue must be drained.  */
Packit 6c4009
      if (!err)
Packit 6c4009
	drain = 0;
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  assert (err == ETIMEDOUT);
Packit 6c4009
	  drain = 1;
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      /* We're still in the list of waiters.  Noone attempted to wake us up,
Packit 6c4009
         i.e. we timed out.  */
Packit 6c4009
      assert (err == ETIMEDOUT);
Packit 6c4009
      __pthread_dequeue (self);
Packit 6c4009
      drain = 0;
Packit 6c4009
    }
Packit 6c4009
  __pthread_spin_unlock (&cond->__lock);
Packit 6c4009
Packit 6c4009
  if (drain)
Packit 6c4009
    __pthread_block (self);
Packit 6c4009
Packit 6c4009
  /* We're almost done.  Remove the unblock hook, restore the previous
Packit 6c4009
     cancellation type, and check for a pending cancellation request.  */
Packit 6c4009
  __pthread_mutex_lock (&self->cancel_lock);
Packit 6c4009
  self->cancel_hook = NULL;
Packit 6c4009
  self->cancel_hook_arg = NULL;
Packit 6c4009
  self->cancel_type = oldtype;
Packit 6c4009
  cancelled = (self->cancel_state == PTHREAD_CANCEL_ENABLE)
Packit 6c4009
      && self->cancel_pending;
Packit 6c4009
  __pthread_mutex_unlock (&self->cancel_lock);
Packit 6c4009
Packit 6c4009
  /* Reacquire MUTEX before returning/cancelling.  */
Packit 6c4009
  __pthread_mutex_lock (mutex);
Packit 6c4009
Packit 6c4009
  if (cancelled)
Packit 6c4009
    __pthread_exit (PTHREAD_CANCELED);
Packit 6c4009
Packit 6c4009
  return err;
Packit 6c4009
}