Add condvars and events via parking lot
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zinnia/mutex.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct condvar {
|
||||||
|
_Atomic(uint32_t) seq;
|
||||||
|
_Atomic(uint32_t) waiters;
|
||||||
|
};
|
||||||
|
|
||||||
|
void condvar_init(struct condvar* cv);
|
||||||
|
void condvar_wait(struct condvar* cv, mutex_t* mutex);
|
||||||
|
void condvar_signal(struct condvar* cv);
|
||||||
|
void condvar_broadcast(struct condvar* cv);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct event {
|
||||||
|
_Atomic(uint32_t) state;
|
||||||
|
};
|
||||||
|
|
||||||
|
void event_init(struct event* event);
|
||||||
|
|
||||||
|
// Sets the event and wakes all waiters.
|
||||||
|
void event_set(struct event* event);
|
||||||
|
|
||||||
|
// Clears the event, so that future waiters block again.
|
||||||
|
void event_reset(struct event* event);
|
||||||
|
|
||||||
|
bool event_is_set(struct event* event);
|
||||||
|
|
||||||
|
// Blocks until the event is set.
|
||||||
|
void event_wait(struct event* event);
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zinnia/atomic.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct task;
|
||||||
|
|
||||||
|
enum park_status {
|
||||||
|
PARK_UNPARKED,
|
||||||
|
PARK_INVALID,
|
||||||
|
PARK_TIMED_OUT,
|
||||||
|
PARK_INTERRUPTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct park_result {
|
||||||
|
enum park_status status;
|
||||||
|
uintptr_t token;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum park_flags {
|
||||||
|
// Queue in front of waiters with the same priority.
|
||||||
|
PARK_FRONT = 1 << 0,
|
||||||
|
PARK_INTERRUPTIBLE = 1 << 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct park_unpark_result {
|
||||||
|
struct task* task; // The task that is being woken up, or nullptr if there was no waiter.
|
||||||
|
bool have_more; // Whether there are more tasks parked on the same key.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parks the current task on `key` if `validate(ctx)` returns true.
|
||||||
|
// `validate` is called with the queue for `key` locked, so it must be short and must not sleep.
|
||||||
|
// Returns the token passed by the waker, or `PARK_INVALID` if `validate` returned false.
|
||||||
|
// Must not be called while holding a spinlock.
|
||||||
|
struct park_result park(const void* key, bool (*validate)(void* ctx), void* ctx, enum park_flags flags);
|
||||||
|
|
||||||
|
// Very similar to `park`, but with an optional `deadline_ns`.
|
||||||
|
// If the deadline is missed, `cancelled` gets called (also optional).
|
||||||
|
struct park_result park_timeout(
|
||||||
|
const void* key,
|
||||||
|
bool (*validate)(void* ctx),
|
||||||
|
void (*cancelled)(void* ctx, bool was_last),
|
||||||
|
void* ctx,
|
||||||
|
enum park_flags flags,
|
||||||
|
const uint64_t* deadline_ns
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wakes the first task parked on `key`.
|
||||||
|
// `callback`, if set, is called with the queue locked before the task is woken, even if there was no waiter.
|
||||||
|
// Its return value is passed to the woken task as the token. Without a callback, the token is 0.
|
||||||
|
// Returns true if a task was woken.
|
||||||
|
bool unpark_one(const void* key, uintptr_t (*callback)(void* ctx, const struct park_unpark_result* result), void* ctx);
|
||||||
|
|
||||||
|
// Wakes all tasks parked on `key` and passes `token` to them. Returns the amount of tasks woken.
|
||||||
|
size_t unpark_all(const void* key, uintptr_t token);
|
||||||
|
|
||||||
|
// Parks the current task as long as `*addr == expected`. Returns false if the value didn't match.
|
||||||
|
bool park_wait(atomic_uint* addr, unsigned int expected);
|
||||||
|
|
||||||
|
// Wakes up to `count` tasks waiting in park_wait() on `addr`. Returns the amount of tasks woken.
|
||||||
|
size_t park_wake(atomic_uint* addr, size_t count);
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct task;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
static inline bool signal_pending(struct task*) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/atomic.h>
|
||||||
|
#include <zinnia/condvar.h>
|
||||||
|
#include <zinnia/park.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void condvar_init(struct condvar* cv) {
|
||||||
|
atomic_store(&cv->seq, 0, ATOMIC_RELAXED);
|
||||||
|
atomic_store(&cv->waiters, 0, ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condvar_wait(struct condvar* cv, mutex_t* mutex) {
|
||||||
|
atomic_fetch_add(&cv->waiters, 1, ATOMIC_SEQ_CST);
|
||||||
|
uint32_t seq = atomic_load(&cv->seq, ATOMIC_SEQ_CST);
|
||||||
|
mutex_unlock(mutex);
|
||||||
|
park_wait(&cv->seq, seq);
|
||||||
|
atomic_fetch_sub(&cv->waiters, 1, ATOMIC_RELAXED);
|
||||||
|
mutex_lock(mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wake(struct condvar* cv, size_t count) {
|
||||||
|
atomic_fetch_add(&cv->seq, 1, ATOMIC_SEQ_CST);
|
||||||
|
if (atomic_load(&cv->waiters, ATOMIC_SEQ_CST) != 0)
|
||||||
|
park_wake(&cv->seq, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condvar_signal(struct condvar* cv) {
|
||||||
|
wake(cv, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condvar_broadcast(struct condvar* cv) {
|
||||||
|
wake(cv, SIZE_MAX);
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/atomic.h>
|
||||||
|
#include <zinnia/event.h>
|
||||||
|
#include <zinnia/park.h>
|
||||||
|
#include <zinnia/percpu.h>
|
||||||
|
#include <zinnia/sched.h>
|
||||||
|
|
||||||
|
constexpr uint32_t EVENT_SET = (1u << 0);
|
||||||
|
constexpr uint32_t EVENT_PARKED = (1u << 1); // At least one task might be parked on the event.
|
||||||
|
|
||||||
|
void event_init(struct event* event) {
|
||||||
|
atomic_store(&event->state, 0, ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void event_set(struct event* event) {
|
||||||
|
uint32_t old = atomic_fetch_or(&event->state, EVENT_SET, ATOMIC_RELEASE);
|
||||||
|
if (!(old & EVENT_PARKED))
|
||||||
|
return;
|
||||||
|
|
||||||
|
atomic_fetch_and(&event->state, ~EVENT_PARKED, ATOMIC_RELAXED);
|
||||||
|
unpark_all(event, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void event_reset(struct event* event) {
|
||||||
|
atomic_fetch_and(&event->state, ~EVENT_SET, ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool event_is_set(struct event* event) {
|
||||||
|
return atomic_load(&event->state, ATOMIC_ACQUIRE) & EVENT_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
void event_wait(struct event* event) {
|
||||||
|
uint32_t state = atomic_load(&event->state, ATOMIC_ACQUIRE);
|
||||||
|
while (!(state & EVENT_SET)) {
|
||||||
|
if (!(state & EVENT_PARKED)) {
|
||||||
|
if (!atomic_compare_exchange_weak(
|
||||||
|
&event->state,
|
||||||
|
&state,
|
||||||
|
state | EVENT_PARKED,
|
||||||
|
ATOMIC_ACQUIRE,
|
||||||
|
ATOMIC_ACQUIRE
|
||||||
|
))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only sleeps if the event is not set, but parked at this point.
|
||||||
|
park_wait(&event->state, EVENT_PARKED);
|
||||||
|
state = atomic_load(&event->state, ATOMIC_ACQUIRE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
kernel_sources += files(
|
kernel_sources += files(
|
||||||
'clock.c',
|
'clock.c',
|
||||||
|
'condvar.c',
|
||||||
'console.c',
|
'console.c',
|
||||||
|
'event.c',
|
||||||
'guid.c',
|
'guid.c',
|
||||||
'initgraph.c',
|
'initgraph.c',
|
||||||
'iovec.c',
|
'iovec.c',
|
||||||
@@ -9,6 +11,7 @@ kernel_sources += files(
|
|||||||
'module.c',
|
'module.c',
|
||||||
'mutex.c',
|
'mutex.c',
|
||||||
'panic.c',
|
'panic.c',
|
||||||
|
'park.c',
|
||||||
'percpu.c',
|
'percpu.c',
|
||||||
'print.c',
|
'print.c',
|
||||||
'radix.c',
|
'radix.c',
|
||||||
|
|||||||
+299
@@ -0,0 +1,299 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/assert.h>
|
||||||
|
#include <zinnia/atomic.h>
|
||||||
|
#include <zinnia/clock.h>
|
||||||
|
#include <zinnia/irq.h>
|
||||||
|
#include <zinnia/park.h>
|
||||||
|
#include <zinnia/percpu.h>
|
||||||
|
#include <zinnia/sched.h>
|
||||||
|
#include <zinnia/signal.h>
|
||||||
|
#include <zinnia/spin.h>
|
||||||
|
#include <zinnia/timer.h>
|
||||||
|
#include <zinnia/utils.h>
|
||||||
|
|
||||||
|
constexpr size_t PARK_BUCKET_BITS = 8;
|
||||||
|
constexpr size_t PARK_BUCKETS = (1 << PARK_BUCKET_BITS);
|
||||||
|
|
||||||
|
enum park_waiter_state : unsigned int {
|
||||||
|
WAITER_QUEUED,
|
||||||
|
WAITER_CLAIMED,
|
||||||
|
WAITER_DONE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct park_waiter {
|
||||||
|
const void* key;
|
||||||
|
struct task* task;
|
||||||
|
uint8_t priority;
|
||||||
|
atomic_uint state;
|
||||||
|
struct park_result result;
|
||||||
|
struct park_waiter* prev;
|
||||||
|
struct park_waiter* next;
|
||||||
|
struct park_waiter* key_prev;
|
||||||
|
struct park_waiter* key_next;
|
||||||
|
struct park_waiter* wake_next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct park_bucket {
|
||||||
|
alignas(64) struct spinlock lock;
|
||||||
|
struct park_waiter* keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct park_bucket buckets[PARK_BUCKETS];
|
||||||
|
|
||||||
|
static struct park_bucket* bucket_for(const void* key) {
|
||||||
|
uint64_t hash = ((uintptr_t)key >> 3) * 0x9E3779B97F4A7C15ull;
|
||||||
|
return &buckets[hash >> (64 - PARK_BUCKET_BITS)];
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct park_waiter* queue_find(struct park_bucket* b, const void* key) {
|
||||||
|
struct park_waiter* head = b->keys;
|
||||||
|
while (head && head->key != key)
|
||||||
|
head = head->key_next;
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void queue_replace_head(struct park_bucket* b, struct park_waiter* old, struct park_waiter* new) {
|
||||||
|
new->key_prev = old->key_prev;
|
||||||
|
new->key_next = old->key_next;
|
||||||
|
if (new->key_prev)
|
||||||
|
new->key_prev->key_next = new;
|
||||||
|
else
|
||||||
|
b->keys = new;
|
||||||
|
if (new->key_next)
|
||||||
|
new->key_next->key_prev = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queues `w` behind all waiters on the same key that are at least as urgent.
|
||||||
|
// With `front`, it's queued in front of waiters on the same key with the same priority instead.
|
||||||
|
// The priority is only considered at the time of queueing.
|
||||||
|
static void queue_insert(struct park_bucket* b, struct park_waiter* w, bool front) {
|
||||||
|
struct park_waiter* head = queue_find(b, w->key);
|
||||||
|
if (!head) {
|
||||||
|
w->prev = nullptr;
|
||||||
|
w->next = nullptr;
|
||||||
|
w->key_prev = nullptr;
|
||||||
|
w->key_next = b->keys;
|
||||||
|
if (b->keys)
|
||||||
|
b->keys->key_prev = w;
|
||||||
|
b->keys = w;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct park_waiter* last = nullptr;
|
||||||
|
struct park_waiter* pos = head;
|
||||||
|
while (pos && (pos->priority > w->priority || (!front && pos->priority == w->priority))) {
|
||||||
|
last = pos;
|
||||||
|
pos = pos->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->prev = last;
|
||||||
|
w->next = pos;
|
||||||
|
if (pos)
|
||||||
|
pos->prev = w;
|
||||||
|
if (last)
|
||||||
|
last->next = w;
|
||||||
|
else
|
||||||
|
queue_replace_head(b, head, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void queue_remove(struct park_bucket* b, struct park_waiter* w) {
|
||||||
|
if (w->next)
|
||||||
|
w->next->prev = w->prev;
|
||||||
|
if (w->prev) {
|
||||||
|
w->prev->next = w->next;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (w->next) {
|
||||||
|
queue_replace_head(b, w, w->next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (w->key_prev)
|
||||||
|
w->key_prev->key_next = w->key_next;
|
||||||
|
else
|
||||||
|
b->keys = w->key_next;
|
||||||
|
if (w->key_next)
|
||||||
|
w->key_next->key_prev = w->key_prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be called with the bucket locksd. `w` must already be removed from the bucket.
|
||||||
|
static void claim(struct park_waiter* w, uintptr_t token) {
|
||||||
|
w->result = (struct park_result){.status = PARK_UNPARKED, .token = token};
|
||||||
|
w->wake_next = nullptr;
|
||||||
|
atomic_store(&w->state, WAITER_CLAIMED, ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wake_claimed(struct park_waiter* w) {
|
||||||
|
while (w) {
|
||||||
|
struct park_waiter* next = w->wake_next;
|
||||||
|
struct task* task = w->task;
|
||||||
|
task_lock(task);
|
||||||
|
atomic_store(&w->state, WAITER_DONE, ATOMIC_RELEASE);
|
||||||
|
sched_wake_locked(task);
|
||||||
|
task_unlock(task);
|
||||||
|
w = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cancel(
|
||||||
|
struct park_bucket* b,
|
||||||
|
struct park_waiter* w,
|
||||||
|
enum park_status status,
|
||||||
|
void (*cancelled)(void* ctx, bool was_last),
|
||||||
|
void* ctx
|
||||||
|
) {
|
||||||
|
spin_lock(&b->lock);
|
||||||
|
const bool queued = atomic_load(&w->state, ATOMIC_RELAXED) == WAITER_QUEUED;
|
||||||
|
if (queued) {
|
||||||
|
queue_remove(b, w);
|
||||||
|
if (cancelled)
|
||||||
|
cancelled(ctx, queue_find(b, w->key) == nullptr);
|
||||||
|
w->result = (struct park_result){.status = status};
|
||||||
|
atomic_store(&w->state, WAITER_DONE, ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
spin_unlock(&b->lock);
|
||||||
|
return queued;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct park_timer {
|
||||||
|
struct timer timer;
|
||||||
|
struct task* task;
|
||||||
|
atomic_bool expired;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void park_timer_fn(struct timer* timer) {
|
||||||
|
struct park_timer* t = containerof(timer, struct park_timer, timer);
|
||||||
|
atomic_store(&t->expired, true, ATOMIC_RELEASE);
|
||||||
|
sched_wake(t->task);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct park_result park_timeout(
|
||||||
|
const void* key,
|
||||||
|
bool (*validate)(void* ctx),
|
||||||
|
void (*cancelled)(void* ctx, bool was_last),
|
||||||
|
void* ctx,
|
||||||
|
enum park_flags flags,
|
||||||
|
const uint64_t* deadline_ns
|
||||||
|
) {
|
||||||
|
struct task* self = percpu_get()->sched.current;
|
||||||
|
assert(self != nullptr, "Tried to park without a scheduler");
|
||||||
|
assert(irq_get_state(), "Tried to park with interrupts disabled");
|
||||||
|
|
||||||
|
struct park_waiter w = {
|
||||||
|
.key = key,
|
||||||
|
.task = self,
|
||||||
|
.priority = atomic_load(&self->priority, ATOMIC_RELAXED),
|
||||||
|
};
|
||||||
|
atomic_init(&w.state, WAITER_QUEUED);
|
||||||
|
|
||||||
|
struct park_bucket* b = bucket_for(key);
|
||||||
|
spin_lock(&b->lock);
|
||||||
|
if (validate && !validate(ctx)) {
|
||||||
|
spin_unlock(&b->lock);
|
||||||
|
return (struct park_result){.status = PARK_INVALID};
|
||||||
|
}
|
||||||
|
if (deadline_ns && clock_get_elapsed_ns() >= *deadline_ns) {
|
||||||
|
if (cancelled)
|
||||||
|
cancelled(ctx, queue_find(b, key) == nullptr);
|
||||||
|
spin_unlock(&b->lock);
|
||||||
|
return (struct park_result){.status = PARK_TIMED_OUT};
|
||||||
|
}
|
||||||
|
queue_insert(b, &w, flags & PARK_FRONT);
|
||||||
|
spin_unlock(&b->lock);
|
||||||
|
|
||||||
|
struct park_timer timer = {.task = self};
|
||||||
|
atomic_init(&timer.expired, false);
|
||||||
|
if (deadline_ns) {
|
||||||
|
timer_init(&timer.timer, park_timer_fn);
|
||||||
|
timer_arm(&timer.timer, *deadline_ns);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sched_block() can return early, so check whether we were actually unparked.
|
||||||
|
// The task may migrate while blocked, the scheduler has to be looked up again every time.
|
||||||
|
while (atomic_load(&w.state, ATOMIC_ACQUIRE) != WAITER_DONE) {
|
||||||
|
enum park_status reason = PARK_UNPARKED;
|
||||||
|
if (atomic_load(&timer.expired, ATOMIC_ACQUIRE))
|
||||||
|
reason = PARK_TIMED_OUT;
|
||||||
|
else if ((flags & PARK_INTERRUPTIBLE) && signal_pending(self))
|
||||||
|
reason = PARK_INTERRUPTED;
|
||||||
|
if (reason != PARK_UNPARKED && cancel(b, &w, reason, cancelled, ctx))
|
||||||
|
break;
|
||||||
|
sched_block(&percpu_get()->sched);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deadline_ns)
|
||||||
|
timer_cancel(&timer.timer);
|
||||||
|
task_lock(self);
|
||||||
|
task_unlock(self);
|
||||||
|
return w.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct park_result park(const void* key, bool (*validate)(void* ctx), void* ctx, enum park_flags flags) {
|
||||||
|
return park_timeout(key, validate, nullptr, ctx, flags, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool unpark_one(const void* key, uintptr_t (*callback)(void* ctx, const struct park_unpark_result* result), void* ctx) {
|
||||||
|
struct park_bucket* b = bucket_for(key);
|
||||||
|
spin_lock(&b->lock);
|
||||||
|
|
||||||
|
struct park_waiter* w = queue_find(b, key);
|
||||||
|
if (w)
|
||||||
|
queue_remove(b, w);
|
||||||
|
|
||||||
|
struct park_unpark_result result = {
|
||||||
|
.task = w ? w->task : nullptr,
|
||||||
|
.have_more = queue_find(b, key) != nullptr,
|
||||||
|
};
|
||||||
|
uintptr_t token = callback ? callback(ctx, &result) : 0;
|
||||||
|
|
||||||
|
if (w)
|
||||||
|
claim(w, token);
|
||||||
|
|
||||||
|
spin_unlock(&b->lock);
|
||||||
|
wake_claimed(w);
|
||||||
|
return w != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t unpark_n(const void* key, size_t count, uintptr_t token) {
|
||||||
|
struct park_bucket* b = bucket_for(key);
|
||||||
|
struct park_waiter* list = nullptr;
|
||||||
|
struct park_waiter** tail = &list;
|
||||||
|
size_t woken = 0;
|
||||||
|
|
||||||
|
spin_lock(&b->lock);
|
||||||
|
struct park_waiter* w;
|
||||||
|
while (woken < count && (w = queue_find(b, key))) {
|
||||||
|
queue_remove(b, w);
|
||||||
|
claim(w, token);
|
||||||
|
*tail = w;
|
||||||
|
tail = &w->wake_next;
|
||||||
|
woken++;
|
||||||
|
}
|
||||||
|
spin_unlock(&b->lock);
|
||||||
|
|
||||||
|
wake_claimed(list);
|
||||||
|
return woken;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t unpark_all(const void* key, uintptr_t token) {
|
||||||
|
return unpark_n(key, SIZE_MAX, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct park_wait_ctx {
|
||||||
|
atomic_uint* addr;
|
||||||
|
unsigned int expected;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool park_wait_validate(void* ctx) {
|
||||||
|
struct park_wait_ctx* c = ctx;
|
||||||
|
return atomic_load(c->addr, ATOMIC_RELAXED) == c->expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool park_wait(atomic_uint* addr, unsigned int expected) {
|
||||||
|
struct park_wait_ctx ctx = {addr, expected};
|
||||||
|
return park(addr, park_wait_validate, &ctx, 0).status != PARK_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t park_wake(atomic_uint* addr, size_t count) {
|
||||||
|
return unpark_n(addr, count, 0);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user