/* coroutine_sema.h - Simple semaphores for coroutine.{h,c} * * Copyright (C) 2024 Luke T. Shumaker * SPDX-Licence-Identifier: AGPL-3.0-or-later */ #include #include #include /** Drain the sema->{head,tail} list. Returns true if cr_getcid() was drained. */ static inline bool drain(cr_sema_t *sema) { assert(!sema->locked); cid_t self = cr_getcid(); enum drain_result { DRAINING, DRAINED_SELF, /* stopped because drained `self` */ DRAINED_ALL, /* stopped because sema->head == NULL */ DRAINED_SOME, /* stopped because sema->cnt == 0 */ } state = DRAINING; do { sema->locked = true; while (state == DRAINING) { if (!sema->head) { state = DRAINED_ALL; } else if (!sema->cnt) { state = DRAINED_SOME; } else { sema->cnt--; cid_t cid = sema->head->val; if (cid == self) state = DRAINED_SELF; else cr_unpause(sema->head->val); sema->head = sema->head->next; if (!sema->head) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" sema->tail = &sema->head; #pragma GCC diagnostic pop } } sema->locked = false; /* If there are still coroutines in sema->head, check * that sema->cnt wasn't incremented between `if * (!sema->cnt)` and `sema->locked = false`. */ } while (state == DRAINED_SOME && sema->cnt); /* If state == DRAINED_SELF, then we better have been the last * item in the list! */ assert(state != DRAINED_SELF || !sema->head); return state == DRAINED_SELF; } void cr_sema_signal(cr_sema_t *sema) { sema->cnt++; if (!sema->locked) drain(sema); } void cr_sema_wait(cr_sema_t *sema) { struct _cr_sema_cid_list self = { .val = cr_getcid(), .next = NULL, }; sema->locked = true; if (!sema->tail) sema->head = &self; else *(sema->tail) = &self; sema->tail = &(self.next); sema->locked = false; if (drain(sema)) /* DRAINED_SELF: (1) No need to pause+yield, (2) we * better have been the last item in the list! */ assert(!self.next); else cr_pause_and_yield(); }