NetBSD/lib/libpthread/pthread_cond.c
ad b5a5e72af1 Mirror a fix made to the kernel's condvars:
After resuming execution, the thread must check to see if it
has been restarted as a result of pthread_cond_signal().  If it
has, but cannot take the wakeup (because of eg a pending Unix
signal or timeout) then try to ensure that another thread sees
it.  This is necessary because there may be multiple waiters,
and at least one should take the wakeup if possible.
2007-04-12 21:36:06 +00:00

400 lines
12 KiB
C

/* $NetBSD: pthread_cond.c,v 1.31 2007/04/12 21:36:06 ad Exp $ */
/*-
* Copyright (c) 2001, 2006, 2007 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Nathan J. Williams and Andrew Doran.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__RCSID("$NetBSD: pthread_cond.c,v 1.31 2007/04/12 21:36:06 ad Exp $");
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include "pthread.h"
#include "pthread_int.h"
#ifdef PTHREAD_COND_DEBUG
#define SDPRINTF(x) DPRINTF(x)
#else
#define SDPRINTF(x)
#endif
int _sys_nanosleep(const struct timespec *, struct timespec *);
extern int pthread__started;
static int pthread_cond_wait_nothread(pthread_t, pthread_mutex_t *,
const struct timespec *);
__strong_alias(__libc_cond_init,pthread_cond_init)
__strong_alias(__libc_cond_signal,pthread_cond_signal)
__strong_alias(__libc_cond_broadcast,pthread_cond_broadcast)
__strong_alias(__libc_cond_wait,pthread_cond_wait)
__strong_alias(__libc_cond_timedwait,pthread_cond_timedwait)
__strong_alias(__libc_cond_destroy,pthread_cond_destroy)
int
pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)
{
pthread__error(EINVAL, "Invalid condition variable attribute",
(attr == NULL) || (attr->ptca_magic == _PT_CONDATTR_MAGIC));
cond->ptc_magic = _PT_COND_MAGIC;
pthread_lockinit(&cond->ptc_lock);
PTQ_INIT(&cond->ptc_waiters);
cond->ptc_mutex = NULL;
return 0;
}
int
pthread_cond_destroy(pthread_cond_t *cond)
{
pthread__error(EINVAL, "Invalid condition variable",
cond->ptc_magic == _PT_COND_MAGIC);
pthread__error(EBUSY, "Destroying condition variable in use",
cond->ptc_mutex == NULL);
cond->ptc_magic = _PT_COND_DEAD;
return 0;
}
int
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
{
pthread_t self;
pthread__error(EINVAL, "Invalid condition variable",
cond->ptc_magic == _PT_COND_MAGIC);
pthread__error(EINVAL, "Invalid mutex",
mutex->ptm_magic == _PT_MUTEX_MAGIC);
pthread__error(EPERM, "Mutex not locked in condition wait",
mutex->ptm_lock == __SIMPLELOCK_LOCKED);
self = pthread__self();
PTHREADD_ADD(PTHREADD_COND_WAIT);
/* Just hang out for a while if threads aren't running yet. */
if (__predict_false(pthread__started == 0))
return pthread_cond_wait_nothread(self, mutex, NULL);
SDPRINTF(("(cond wait %p) Waiting on %p, mutex %p\n",
self, cond, mutex));
if (__predict_false(self->pt_cancel))
pthread_exit(PTHREAD_CANCELED);
pthread_spinlock(self, &cond->ptc_lock);
if (cond->ptc_mutex == NULL)
cond->ptc_mutex = mutex;
else {
#ifdef ERRORCHECK
pthread__error(EINVAL,
"Multiple mutexes used for condition wait",
cond->ptc_mutex == mutex);
#endif
}
PTQ_INSERT_HEAD(&cond->ptc_waiters, self, pt_sleep);
self->pt_signalled = 0;
self->pt_sleeponq = 1;
self->pt_sleepobj = &cond->ptc_waiters;
pthread_mutex_unlock(mutex);
(void)pthread__park(self, &cond->ptc_lock, &cond->ptc_waiters,
NULL, 1, &mutex->ptm_blocked);
if (PTQ_EMPTY(&cond->ptc_waiters))
cond->ptc_mutex = NULL;
pthread_spinunlock(self, &cond->ptc_lock);
pthread_mutex_lock(mutex);
if (__predict_false(self->pt_cancel)) {
if (self->pt_signalled)
pthread_cond_signal(cond);
pthread_exit(PTHREAD_CANCELED);
}
SDPRINTF(("(cond wait %p) Woke up on %p, mutex %p\n",
self, cond, mutex));
return 0;
}
int
pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime)
{
pthread_t self;
int retval;
pthread__error(EINVAL, "Invalid condition variable",
cond->ptc_magic == _PT_COND_MAGIC);
pthread__error(EINVAL, "Invalid mutex",
mutex->ptm_magic == _PT_MUTEX_MAGIC);
pthread__error(EPERM, "Mutex not locked in condition wait",
mutex->ptm_lock == __SIMPLELOCK_LOCKED);
pthread__error(EINVAL, "Invalid wait time",
(abstime->tv_sec >= 0) &&
(abstime->tv_nsec >= 0) && (abstime->tv_nsec < 1000000000));
self = pthread__self();
PTHREADD_ADD(PTHREADD_COND_TIMEDWAIT);
/* Just hang out for a while if threads aren't running yet. */
if (__predict_false(pthread__started == 0))
return pthread_cond_wait_nothread(self, mutex, abstime);
SDPRINTF(("(cond timed wait %p) Waiting on %p until %d.%06ld\n",
self, cond, abstime->tv_sec, abstime->tv_nsec/1000));
if (__predict_false(self->pt_cancel))
pthread_exit(PTHREAD_CANCELED);
pthread_spinlock(self, &cond->ptc_lock);
if (cond->ptc_mutex == NULL)
cond->ptc_mutex = mutex;
else {
#ifdef ERRORCHECK
pthread__error(EINVAL,
"Multiple mutexes used for condition wait",
cond->ptc_mutex == mutex);
#endif
}
PTQ_INSERT_HEAD(&cond->ptc_waiters, self, pt_sleep);
self->pt_signalled = 0;
self->pt_sleeponq = 1;
self->pt_sleepobj = &cond->ptc_waiters;
pthread_mutex_unlock(mutex);
retval = pthread__park(self, &cond->ptc_lock, &cond->ptc_waiters,
abstime, 1, &mutex->ptm_blocked);
if (PTQ_EMPTY(&cond->ptc_waiters))
cond->ptc_mutex = NULL;
pthread_spinunlock(self, &cond->ptc_lock);
SDPRINTF(("(cond timed wait %p) Woke up on %p, mutex %p\n",
self, cond));
SDPRINTF(("(cond timed wait %p) %s\n",
self, (retval == ETIMEDOUT) ? "(timed out)" : ""));
pthread_mutex_lock(mutex);
if (__predict_false(self->pt_cancel | retval)) {
if (self->pt_signalled)
pthread_cond_signal(cond);
if (self->pt_cancel)
pthread_exit(PTHREAD_CANCELED);
}
return retval;
}
int
pthread_cond_signal(pthread_cond_t *cond)
{
pthread_t self, signaled;
pthread_mutex_t *mutex;
pthread__error(EINVAL, "Invalid condition variable",
cond->ptc_magic == _PT_COND_MAGIC);
PTHREADD_ADD(PTHREADD_COND_SIGNAL);
SDPRINTF(("(cond signal %p) Signaling %p\n",
pthread__self(), cond));
if (PTQ_EMPTY(&cond->ptc_waiters))
return 0;
self = pthread__self();
pthread_spinlock(self, &cond->ptc_lock);
/*
* Find a thread that is still blocked (no pending wakeup).
* A wakeup can be pending if we have interrupted unpark_all
* as it releases the interlock.
*/
PTQ_FOREACH(signaled, &cond->ptc_waiters, pt_sleep) {
if (signaled->pt_sleepobj != NULL)
break;
}
if (__predict_false(signaled == NULL)) {
cond->ptc_mutex = NULL;
pthread_spinunlock(self, &cond->ptc_lock);
return 0;
}
/*
* Pull the thread off the queue, and set pt_signalled.
*
* After resuming execution, the thread must check to see if it
* has been restarted as a result of pthread_cond_signal(). If it
* has, but cannot take the wakeup (because of eg a pending Unix
* signal or timeout) then try to ensure that another thread sees
* it. This is necessary because there may be multiple waiters,
* and at least one should take the wakeup if possible.
*/
PTQ_REMOVE(&cond->ptc_waiters, signaled, pt_sleep);
mutex = cond->ptc_mutex;
if (PTQ_EMPTY(&cond->ptc_waiters))
cond->ptc_mutex = NULL;
signaled->pt_signalled = 1;
/*
* For all valid uses of pthread_cond_signal(), the caller will
* hold the mutex that the target is using to synchronize with.
* To avoid the target awakening and immediatley blocking on the
* mutex, transfer the thread to be awoken to the mutex's waiters
* list. The waiter will be set running when the caller (this
* thread) releases the mutex.
*/
if (self->pt_mutexhint != NULL && self->pt_mutexhint == mutex) {
pthread_spinunlock(self, &cond->ptc_lock);
pthread_spinlock(self, &mutex->ptm_interlock);
PTQ_INSERT_HEAD(&mutex->ptm_blocked, signaled, pt_sleep);
pthread_spinunlock(self, &mutex->ptm_interlock);
} else {
pthread__unpark(self, &cond->ptc_lock,
&cond->ptc_waiters, signaled);
}
PTHREADD_ADD(PTHREADD_COND_WOKEUP);
return 0;
}
int
pthread_cond_broadcast(pthread_cond_t *cond)
{
pthread_t self, signaled, next;
pthread_mutex_t *mutex;
pthread__error(EINVAL, "Invalid condition variable",
cond->ptc_magic == _PT_COND_MAGIC);
PTHREADD_ADD(PTHREADD_COND_BROADCAST);
SDPRINTF(("(cond signal %p) Broadcasting %p\n",
pthread__self(), cond));
if (PTQ_EMPTY(&cond->ptc_waiters))
return 0;
self = pthread__self();
pthread_spinlock(self, &cond->ptc_lock);
mutex = cond->ptc_mutex;
cond->ptc_mutex = NULL;
/*
* Try to transfer waiters to the mutex's waiters list (see
* pthread_cond_signal()). Only transfer waiters for which
* there is no pending wakeup.
*/
if (self->pt_mutexhint != NULL && self->pt_mutexhint == mutex) {
pthread_spinlock(self, &mutex->ptm_interlock);
for (signaled = PTQ_FIRST(&cond->ptc_waiters);
signaled != NULL;
signaled = next) {
next = PTQ_NEXT(signaled, pt_sleep);
if (__predict_false(signaled->pt_sleepobj == NULL))
continue;
PTQ_REMOVE(&cond->ptc_waiters, signaled, pt_sleep);
PTQ_INSERT_HEAD(&mutex->ptm_blocked, signaled,
pt_sleep);
}
pthread_spinunlock(self, &mutex->ptm_interlock);
pthread_spinunlock(self, &cond->ptc_lock);
} else
pthread__unpark_all(self, &cond->ptc_lock, &cond->ptc_waiters);
PTHREADD_ADD(PTHREADD_COND_WOKEUP);
return 0;
}
int
pthread_condattr_init(pthread_condattr_t *attr)
{
attr->ptca_magic = _PT_CONDATTR_MAGIC;
return 0;
}
int
pthread_condattr_destroy(pthread_condattr_t *attr)
{
pthread__error(EINVAL, "Invalid condition variable attribute",
attr->ptca_magic == _PT_CONDATTR_MAGIC);
attr->ptca_magic = _PT_CONDATTR_DEAD;
return 0;
}
/* Utility routine to hang out for a while if threads haven't started yet. */
static int
pthread_cond_wait_nothread(pthread_t self, pthread_mutex_t *mutex,
const struct timespec *abstime)
{
struct timespec now, diff;
int retval;
if (abstime == NULL) {
diff.tv_sec = 99999999;
diff.tv_nsec = 0;
} else {
clock_gettime(CLOCK_REALTIME, &now);
if (timespeccmp(abstime, &now, <))
timespecclear(&diff);
else
timespecsub(abstime, &now, &diff);
}
do {
pthread__testcancel(self);
pthread_mutex_unlock(mutex);
retval = _sys_nanosleep(&diff, NULL);
pthread_mutex_lock(mutex);
} while (abstime == NULL && retval == 0);
pthread__testcancel(self);
if (retval == 0)
return ETIMEDOUT;
else
/* spurious wakeup */
return 0;
}