blob: 4ab016ab027f9dad46c6de03b6494b906b74965c [file] [log] [blame]
#include "futex_impl.h"
#include "pthread_impl.h"
/*
* struct waiter
*
* Waiter objects have automatic storage on the waiting thread, and
* are used in building a linked list representing waiters currently
* waiting on the condition variable or a group of waiters woken
* together by a broadcast or signal; in the case of signal, this is a
* degenerate list of one member.
*
* Waiter lists attached to the condition variable itself are
* protected by the lock on the cv. Detached waiter lists are never
* modified again, but can only be traversed in reverse order, and are
* protected by the "barrier" locks in each node, which are unlocked
* in turn to control wake order.
*/
struct waiter {
struct waiter *prev, *next;
atomic_int state;
atomic_int barrier;
atomic_int* notify;
};
enum {
WAITING,
SIGNALED,
LEAVING,
};
int pthread_cond_timedwait(pthread_cond_t* restrict c, pthread_mutex_t* restrict m,
const struct timespec* restrict ts) {
int e, clock = c->_c_clock, oldstate, tmp;
if ((m->_m_type != PTHREAD_MUTEX_NORMAL) &&
(m->_m_lock & PTHREAD_MUTEX_OWNED_LOCK_MASK) != __thread_get_tid())
return EPERM;
if (ts && ts->tv_nsec >= 1000000000UL)
return EINVAL;
lock(&c->_c_lock);
int seq = 2;
struct waiter node = {
.barrier = ATOMIC_VAR_INIT(seq),
.state = ATOMIC_VAR_INIT(WAITING),
};
atomic_int* fut = &node.barrier;
/* Add our waiter node onto the condvar's list. We add the node to the
* head of the list, but this is logically the end of the queue. */
node.next = c->_c_head;
c->_c_head = &node;
if (!c->_c_tail)
c->_c_tail = &node;
else
node.next->prev = &node;
unlock(&c->_c_lock);
pthread_mutex_unlock(m);
/* Wait to be signaled. There are multiple ways this loop could exit:
* 1) After being woken by __private_cond_signal().
* 2) After being woken by pthread_mutex_unlock(), after we were
* requeued from the condvar's futex to the mutex's futex (by
* pthread_cond_timedwait() in another thread).
* 3) After a timeout.
* 4) On Linux, interrupted by an asynchronous signal. This does
* not apply on Zircon. */
do
e = __timedwait(fut, seq, clock, ts);
while (*fut == seq && !e);
oldstate = a_cas_shim(&node.state, WAITING, LEAVING);
if (oldstate == WAITING) {
/* The wait timed out. So far, this thread was not signaled
* by pthread_cond_signal()/broadcast() -- this thread was
* able to move state.node out of the WAITING state before any
* __private_cond_signal() call could do that.
*
* This thread must therefore remove the waiter node from the
* list itself. */
/* Access to cv object is valid because this waiter was not
* yet signaled and a new signal/broadcast cannot return
* after seeing a LEAVING waiter without getting notified
* via the futex notify below. */
lock(&c->_c_lock);
/* Remove our waiter node from the list. */
if (c->_c_head == &node)
c->_c_head = node.next;
else if (node.prev)
node.prev->next = node.next;
if (c->_c_tail == &node)
c->_c_tail = node.prev;
else if (node.next)
node.next->prev = node.prev;
unlock(&c->_c_lock);
/* It is possible that __private_cond_signal() saw our waiter node
* after we set node.state to LEAVING but before we removed the
* node from the list. If so, it will have set node.notify and
* will be waiting on it, and we need to wake it up.
*
* This is rather complex. An alternative would be to eliminate
* the node.state field and always claim _c_lock if we could have
* got a timeout. However, that presumably has higher overhead
* (since it contends _c_lock and involves more atomic ops). */
if (node.notify) {
if (atomic_fetch_add(node.notify, -1) == 1)
__wake(node.notify, 1);
}
} else {
/* This thread was at least partially signaled by
* pthread_cond_signal()/broadcast(). That might have raced
* with a timeout, so we need to wait for this thread to be
* fully signaled. We need to wait until another thread sets
* node.barrier to 0. (This lock() call will also set
* node.barrier to non-zero, but that side effect is
* unnecessary here.) */
lock(&node.barrier);
}
/* Errors locking the mutex override any existing error, since the
* caller must see them to know the state of the mutex. */
if ((tmp = pthread_mutex_lock(m)))
e = tmp;
if (oldstate == WAITING)
goto done;
/* By this point, our part of the waiter list cannot change further.
* It has been unlinked from the condvar by __private_cond_signal().
* It consists only of waiters that were woken explicitly by
* pthread_cond_signal()/broadcast(). Any timed-out waiters would have
* removed themselves from the list before __private_cond_signal()
* signaled the first node.barrier in our list.
*
* It is therefore safe now to read node.next and node.prev without
* holding _c_lock. */
/* As an optimization, we only update _m_waiters at the beginning and
* end of the woken list. */
if (!node.next)
atomic_fetch_add(&m->_m_waiters, 1);
/* Unlock the barrier that's holding back the next waiter, and
* either wake it or requeue it to the mutex. */
if (node.prev)
unlock_requeue(&node.prev->barrier, &m->_m_lock);
else
atomic_fetch_sub(&m->_m_waiters, 1);
done:
return e;
}
/* This will wake upto |n| threads that are waiting on the condvar. This
* is used to implement pthread_cond_signal() (for n=1) and
* pthread_cond_broadcast() (for n=-1). */
void __private_cond_signal(void* condvar, int n) {
pthread_cond_t* c = condvar;
struct waiter *p, *first = 0;
atomic_int ref = ATOMIC_VAR_INIT(0);
int cur;
lock(&c->_c_lock);
for (p = c->_c_tail; n && p; p = p->prev) {
if (a_cas_shim(&p->state, WAITING, SIGNALED) != WAITING) {
/* This waiter timed out, and it marked itself as in the
* LEAVING state. However, it hasn't yet claimed _c_lock
* (since we claimed the lock first) and so it hasn't yet
* removed itself from the list. We will wait for the waiter
* to remove itself from the list and to notify us of that. */
atomic_fetch_add(&ref, 1);
p->notify = &ref;
} else {
n--;
if (!first)
first = p;
}
}
/* Split the list, leaving any remainder on the cv. */
if (p) {
if (p->next)
p->next->prev = 0;
p->next = 0;
} else {
c->_c_head = 0;
}
c->_c_tail = p;
unlock(&c->_c_lock);
/* Wait for any waiters in the LEAVING state to remove
* themselves from the list before returning or allowing
* signaled threads to proceed. */
while ((cur = atomic_load(&ref)))
__wait(&ref, 0, cur);
/* Allow first signaled waiter, if any, to proceed. */
if (first)
unlock(&first->barrier);
}