struct futex { volatile int lock; volatile int count; }; #define LARGE_ENOUGH_NEGATIVE -0x7fffffff #ifdef __cplusplus extern "C" { #endif static inline void futex_init(struct futex* pf, int count) { pf->lock = 0; pf->count = count; } /* Return value: * 0: okay * ETIMEDOUT: timeout * EINTR: interrupted */ static inline int futex_sema_down(struct futex* pf, struct timespec* timeout, bool interruptable) { int n = atomic_add(&pf->count, -1); if (n <= 0) { retry: if (0 == sys_futex(&pf->lock, FUTEX_WAIT, 0, timeout)) { return 0; } switch (errno) { case ETIMEDOUT: atomic_add(&pf->count, 1); return ETIMEDOUT; case EINTR: if (!interruptable) goto retry; atomic_add(&pf->count, 1); return EINTR; default: RaiseError(IMPOSSIBLE__Can_not_lock_in_futex_sema_down); } } return 0; } /* Return value: * 1: wake up some waiter * 0: none is waiting */ static inline int futex_sema_up(struct futex* pf) { int retry; int n = atomic_add(&pf->count, 1); if (n < 0) { retry = 10; while (1 != (n=sys_futex(&pf->lock, FUTEX_WAKE, 1, NULL))) { /* it means the downer decreases the count but not yet start waiting * --- may be interrupted near the retry label in the above function; * so we have to wait and retry. */ if (retry --) { nop(); } else { retry = 10; thread_yield(); } } return n; } return 0; } /* Return value: * 0: okay * ETIMEDOUT: timeout * EINTR: interrupted */ static inline int futex_cond_wait(struct futex* pf, struct timespec* timeout, bool interruptable) { /* I dont know whether it is a bug of linux kernel. * Sometimes, sys_futex(.., FUTEX_WAIT, ..) returns 0, but the condition is not satisfied. * So we have to check the condition again after return. */ while (0 < AtomicGetValue(pf->count)) { sys_futex(&pf->lock, FUTEX_WAIT, 0, timeout); switch (errno) { case ETIMEDOUT: return ETIMEDOUT; case EINTR: if (interruptable) { return EINTR; } default: break; } } return 0; // int nnn; // int n = AtomicGetValue(pf->count); // if (0 != n) { //retry: // //n = sys_futex(&pf->lock, FUTEX_WAIT, 0, timeout); // if (0 == sys_futex(&pf->lock, FUTEX_WAIT, 0, timeout)) { // nnn = errno; // int lock = AtomicGetValue(pf->lock); // int count = AtomicGetValue(pf->count); // UNLIKELY_IF (EAGAIN == errno) { // goto retry; // } // ASSERT_EQUAL(nnn, 0); // ASSERT_EQUAL(count, 0); // return 0; // } // // // switch (errno) { // case 0: // //ASSERT_EQUAL(pf->count, 0); // return 0; // case ETIMEDOUT: // return ETIMEDOUT; // case EINTR: // if (!interruptable) // goto retry; // return EINTR; // case EWOULDBLOCK: // // already signaled // nnn = AtomicGetValue(pf->lock); // ASSERT_EQUAL(nnn, 1); // return 0; // default: // RaiseError(IMPOSSIBLE__Can_not_lock_in_futex_cond_wait); // } // } // return 0; } /* Return value: * the number of woken waiters */ static inline int futex_cond_signal(struct futex* pf) { int n = atomic_add(&pf->count, -1); if (1 == n) { pf->lock = 1; mfence_c(); return sys_futex(&pf->lock, FUTEX_WAKE, 65535, NULL); // I hope 65535 is enough to wake up all } return 0; } static inline int futex_cond_revoke(struct futex* /*pf*/) { // TODO: return 0; } /* Return value: * 0: okay * ETIMEDOUT: timeout * EINTR: interrupted */ static inline int futex_event_wait(struct futex* pf, struct timespec* timeout, bool interruptable) { int n = atomic_add(&pf->count, 1); if (0 <= n) { retry: if (0 == sys_futex(&pf->lock, FUTEX_WAIT, 0, timeout)) return 0; switch (errno) { case ETIMEDOUT: atomic_add(&pf->count, -1); return ETIMEDOUT; case EINTR: if (!interruptable) goto retry; atomic_add(&pf->count, -1); return EINTR; default: RaiseError(IMPOSSIBLE__Can_not_lock_in_futex_sema_down); } } else { // else signaled AtomicSetValue(pf->count, LARGE_ENOUGH_NEGATIVE); } return 0; } /* Return value: * the number of waiters if any */ static inline int futex_event_signal(struct futex* pf, bool reset) { int m, n, retry; n = AtomicSetValue(pf->count, reset ? 0 : LARGE_ENOUGH_NEGATIVE); if (0 < n) { retry = 10; m = n; do { n -= sys_futex(&pf->lock, FUTEX_WAKE, n, NULL); if (0 == n) return m; if (retry --) { nop(); } else { retry = 10; thread_yield(); } } while (1); } return 0; } static inline void futex_event_reset(struct futex* pf) { int n, retry = 10; do { n = AtomicSetValueIf(pf->count, 0, LARGE_ENOUGH_NEGATIVE); if (0<=n || LARGE_ENOUGH_NEGATIVE==n) { return; } if (retry --) { nop(); } else { retry = 10; thread_yield(); } } while (1); } #ifdef __cplusplus } #endif