| #include <threads.h> |
| |
| #include <errno.h> |
| |
| #include <lib/sync/mutex.h> |
| |
| #include "futex_impl.h" |
| #include "libc.h" |
| #include "threads_impl.h" |
| |
| struct waiter { |
| struct waiter *prev, *next; |
| atomic_int state; |
| atomic_int barrier; |
| atomic_int* notify; |
| }; |
| |
| enum { |
| WAITING, |
| LEAVING, |
| }; |
| |
| int cnd_timedwait(cnd_t* restrict c, mtx_t* restrict mutex, |
| const struct timespec* restrict ts) __TA_NO_THREAD_SAFETY_ANALYSIS { |
| sync_mutex_t* m = (sync_mutex_t*)mutex; |
| int e, clock = c->_c_clock, oldstate; |
| |
| if (ts && ts->tv_nsec >= 1000000000UL) |
| return thrd_error; |
| |
| 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); |
| |
| sync_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 sync_mutex_unlock(), after we were |
| * requeued from the condvar's futex to the mutex's futex (by |
| * cnd_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 |
| * cnd_signal()/cnd_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) |
| _zx_futex_wake(node.notify, 1); |
| } |
| } else { |
| /* Lock barrier first to control wake order. */ |
| lock(&node.barrier); |
| } |
| |
| /* We must leave the mutex in the "locked with waiters" state here. |
| * There are two reasons for that: |
| * 1) If we do the unlock_requeue() below, a condvar waiter will be |
| * requeued to the mutex's futex. We need to ensure that it will |
| * be signaled by sync_mutex_unlock() in future. |
| * 2) If the current thread was woken via an unlock_requeue() + |
| * sync_mutex_unlock(), there *might* be another thread waiting for |
| * the mutex after us in the queue. We need to ensure that it |
| * will be signaled by sync_mutex_unlock() in future. */ |
| sync_mutex_lock_with_waiter(m); |
| |
| /* 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 |
| * cnd_signal()/cnd_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. */ |
| |
| if (oldstate != WAITING && node.prev) { |
| /* Unlock the barrier that's holding back the next waiter, and |
| * requeue it to the mutex so that it will be woken when the |
| * mutex is unlocked. */ |
| unlock_requeue(&node.prev->barrier, &m->futex); |
| } |
| |
| switch (e) { |
| case 0: |
| return 0; |
| case EINVAL: |
| return thrd_error; |
| case ETIMEDOUT: |
| return thrd_timedout; |
| default: |
| // No other error values are permissible from __timedwait_cp(); |
| __builtin_trap(); |
| } |
| } |