| #include <assert.h> |
| #include <threads.h> |
| |
| #include "futex_impl.h" |
| |
| enum { |
| STATE_INIT = 0, // we're the first; run init |
| STATE_WAIT = 1, // another thread is running init; wait |
| STATE_DONE = 2, // another thread finished running init; just return |
| STATE_WAKE = 3, // another thread is running init, waiters present; wait |
| }; |
| |
| static_assert(STATE_INIT == ONCE_FLAG_INIT, ""); |
| |
| static void once_full(once_flag* control, void (*init)(void)) { |
| for (;;) |
| switch (a_cas_shim(control, STATE_INIT, STATE_WAIT)) { |
| case STATE_INIT: |
| init(); |
| |
| if (atomic_exchange(control, STATE_DONE) == STATE_WAKE) |
| _zx_futex_wake(control, UINT32_MAX); |
| return; |
| case STATE_WAIT: |
| /* If this fails, so will __wait. */ |
| a_cas_shim(control, STATE_WAIT, STATE_WAKE); |
| case STATE_WAKE: |
| __wait(control, NULL, STATE_WAKE); |
| continue; |
| case STATE_DONE: |
| return; |
| } |
| } |
| |
| void call_once(once_flag* control, void (*init)(void)) { |
| /* Return immediately if init finished before, but ensure that |
| * effects of the init routine are visible to the caller. */ |
| if (atomic_load(control) == STATE_DONE) { |
| atomic_thread_fence(memory_order_seq_cst); |
| return; |
| } |
| once_full(control, init); |
| } |