X-Git-Url: http://nsz.repo.hu/git/?a=blobdiff_plain;f=src%2Fthread%2Fpthread_cond_timedwait.c;h=6b761455c47f0f8c8afde4e2d57768208efc06ba;hb=3f701faace7addc75d16dea8a6cd769fa5b3f260;hp=7aaba95475957719fa0119eb9247bc02ed32da66;hpb=37195db8ec31300a87bc7ec09d2adcf299e9203d;p=musl diff --git a/src/thread/pthread_cond_timedwait.c b/src/thread/pthread_cond_timedwait.c index 7aaba954..6b761455 100644 --- a/src/thread/pthread_cond_timedwait.c +++ b/src/thread/pthread_cond_timedwait.c @@ -10,12 +10,10 @@ * 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 - * protected by the associated mutex. The hand-off between protections - * is handled by a "barrier" lock in each node, which disallows - * signaled waiters from making forward progress to the code that will - * access the list using the mutex until the list is in a consistent - * state and the cv lock as been released. + * 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. * * Since process-shared cond var semantics do not necessarily allow * one thread to see another's automatic storage (they may be in @@ -26,11 +24,8 @@ struct waiter { struct waiter *prev, *next; - int state, barrier, requeued, mutex_ret; - int *notify; - pthread_mutex_t *mutex; - pthread_cond_t *cond; - int shared; + volatile int state, barrier; + volatile int *notify; }; /* Self-synchronized-destruction-safe lock functions */ @@ -50,96 +45,25 @@ static inline void unlock(volatile int *l) __wake(l, 1, 1); } +static inline void unlock_requeue(volatile int *l, volatile int *r, int w) +{ + a_store(l, 0); + if (w) __wake(l, 1, 1); + else __syscall(SYS_futex, l, FUTEX_REQUEUE|FUTEX_PRIVATE, 0, 1, r) != -ENOSYS + || __syscall(SYS_futex, l, FUTEX_REQUEUE, 0, 1, r); +} + enum { WAITING, SIGNALED, LEAVING, }; -static void unwait(void *arg) +int __pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict m, const struct timespec *restrict ts) { - struct waiter *node = arg, *p; - - if (node->shared) { - pthread_cond_t *c = node->cond; - pthread_mutex_t *m = node->mutex; - if (a_fetch_add(&c->_c_waiters, -1) == -0x7fffffff) - __wake(&c->_c_waiters, 1, 0); - node->mutex_ret = pthread_mutex_lock(m); - return; - } - - int oldstate = a_cas(&node->state, WAITING, LEAVING); - - if (oldstate == WAITING) { - /* 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. */ - - pthread_cond_t *c = node->cond; - lock(&c->_c_lock); - - 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); - - if (node->notify) { - if (a_fetch_add(node->notify, -1)==1) - __wake(node->notify, 1, 1); - } - } - - node->mutex_ret = pthread_mutex_lock(node->mutex); - - if (oldstate == WAITING) return; - - /* If the mutex can't be locked, we're in big trouble because - * it's all that protects access to the shared list state. - * In order to prevent catastrophic stack corruption from - * unsynchronized access, simply deadlock. */ - if (node->mutex_ret && node->mutex_ret != EOWNERDEAD) - for (;;) lock(&(int){0}); - - /* Wait until control of the list has been handed over from - * the cv lock (signaling thread) to the mutex (waiters). */ - lock(&node->barrier); - - /* If this thread was requeued to the mutex, undo the extra - * waiter count that was added to the mutex. */ - if (node->requeued) a_dec(&node->mutex->_m_waiters); - - /* Find a thread to requeue to the mutex, starting from the - * end of the list (oldest waiters). */ - for (p=node; p->next; p=p->next); - if (p==node) p=node->prev; - for (; p && p->requeued; p=p->prev); - if (p==node) p=node->prev; - if (p) { - p->requeued = 1; - a_inc(&node->mutex->_m_waiters); - /* The futex requeue command cannot requeue from - * private to shared, so for process-shared mutexes, - * simply wake the target. */ - int wake = node->mutex->_m_type & 128; - __syscall(SYS_futex, &p->state, FUTEX_REQUEUE|128, - wake, 1, &node->mutex->_m_lock) != -EINVAL - || __syscall(SYS_futex, &p->state, FUTEX_REQUEUE, - 0, 1, &node->mutex->_m_lock); - } - - /* Remove this thread from the list. */ - if (node->next) node->next->prev = node->prev; - if (node->prev) node->prev->next = node->next; -} - -int pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict m, const struct timespec *restrict ts) -{ - struct waiter node = { .cond = c, .mutex = m }; - int e, seq, *fut, clock = c->_c_clock; + struct waiter node = { 0 }; + int e, seq, clock = c->_c_clock, cs, shared=0, oldstate, tmp; + volatile int *fut; if ((m->_m_type&15) && (m->_m_lock&INT_MAX) != __pthread_self()->tid) return EPERM; @@ -147,19 +71,19 @@ int pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict if (ts && ts->tv_nsec >= 1000000000UL) return EINVAL; - pthread_testcancel(); + __pthread_testcancel(); if (c->_c_shared) { - node.shared = 1; + shared = 1; fut = &c->_c_seq; seq = c->_c_seq; a_inc(&c->_c_waiters); } else { lock(&c->_c_lock); - node.barrier = 1; - fut = &node.state; - seq = node.state = WAITING; + seq = node.barrier = 2; + fut = &node.barrier; + node.state = WAITING; node.next = c->_c_head; c->_c_head = &node; if (!c->_c_tail) c->_c_tail = &node; @@ -168,35 +92,101 @@ int pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict unlock(&c->_c_lock); } - pthread_mutex_unlock(m); + __pthread_mutex_unlock(m); - do e = __timedwait(fut, seq, clock, ts, unwait, &node, !node.shared); + __pthread_setcancelstate(PTHREAD_CANCEL_MASKED, &cs); + if (cs == PTHREAD_CANCEL_DISABLE) __pthread_setcancelstate(cs, 0); + + do e = __timedwait_cp(fut, seq, clock, ts, !shared); while (*fut==seq && (!e || e==EINTR)); if (e == EINTR) e = 0; - unwait(&node); + if (shared) { + /* Suppress cancellation if a signal was potentially + * consumed; this is a legitimate form of spurious + * wake even if not. */ + if (e == ECANCELED && c->_c_seq != seq) e = 0; + if (a_fetch_add(&c->_c_waiters, -1) == -0x7fffffff) + __wake(&c->_c_waiters, 1, 0); + oldstate = WAITING; + goto relock; + } + + oldstate = a_cas(&node.state, WAITING, LEAVING); + + if (oldstate == WAITING) { + /* 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. */ - return node.mutex_ret ? node.mutex_ret : e; + lock(&c->_c_lock); + + 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); + + if (node.notify) { + if (a_fetch_add(node.notify, -1)==1) + __wake(node.notify, 1, 1); + } + } else { + /* Lock barrier first to control wake order. */ + lock(&node.barrier); + } + +relock: + /* Errors locking the mutex override any existing error or + * cancellation, 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; + + if (!node.next && !(m->_m_type & 8)) + a_inc(&m->_m_waiters); + + /* Unlock the barrier that's holding back the next waiter, and + * either wake it or requeue it to the mutex. */ + if (node.prev) { + int val = m->_m_lock; + if (val>0) a_cas(&m->_m_lock, val, val|0x80000000); + unlock_requeue(&node.prev->barrier, &m->_m_lock, m->_m_type & (8|128)); + } else if (!(m->_m_type & 8)) { + a_dec(&m->_m_waiters); + } + + /* Since a signal was consumed, cancellation is not permitted. */ + if (e == ECANCELED) e = 0; + +done: + __pthread_setcancelstate(cs, 0); + + if (e == ECANCELED) { + __pthread_testcancel(); + __pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0); + } + + return e; } int __private_cond_signal(pthread_cond_t *c, int n) { - struct waiter *p, *q=0; - int ref = 0, cur; + struct waiter *p, *first=0; + volatile int ref = 0; + int cur; lock(&c->_c_lock); for (p=c->_c_tail; n && p; p=p->prev) { - /* The per-waiter-node barrier lock is held at this - * point, so while the following CAS may allow forward - * progress in the target thread, it doesn't allow - * access to the waiter list yet. Ideally the target - * does not run until the futex wake anyway. */ if (a_cas(&p->state, WAITING, SIGNALED) != WAITING) { ref++; p->notify = &ref; } else { n--; - if (!q) q=p; + if (!first) first=p; } } /* Split the list, leaving any remainder on the cv. */ @@ -214,12 +204,10 @@ int __private_cond_signal(pthread_cond_t *c, int n) * signaled threads to proceed. */ while ((cur = ref)) __wait(&ref, 0, cur, 1); - /* Wake the first signaled thread and unlock the per-waiter - * barriers preventing their forward progress. */ - for (p=q; p; p=q) { - q = p->prev; - if (!p->next) __wake(&p->state, 1, 1); - unlock(&p->barrier); - } + /* Allow first signaled waiter, if any, to proceed. */ + if (first) unlock(&first->barrier); + return 0; } + +weak_alias(__pthread_cond_timedwait, pthread_cond_timedwait);