diff options
Diffstat (limited to 'libcr_ipc')
-rw-r--r-- | libcr_ipc/chan.c | 151 | ||||
-rw-r--r-- | libcr_ipc/include/libcr_ipc/chan.h | 112 | ||||
-rw-r--r-- | libcr_ipc/include/libcr_ipc/rpc.h | 2 | ||||
-rw-r--r-- | libcr_ipc/tests/test_chan.c | 8 | ||||
-rw-r--r-- | libcr_ipc/tests/test_mutex.c | 6 | ||||
-rw-r--r-- | libcr_ipc/tests/test_rpc.c | 12 | ||||
-rw-r--r-- | libcr_ipc/tests/test_select.c | 17 | ||||
-rw-r--r-- | libcr_ipc/tests/test_sema.c | 18 |
8 files changed, 156 insertions, 170 deletions
diff --git a/libcr_ipc/chan.c b/libcr_ipc/chan.c index 6cbe890..b7ecfc8 100644 --- a/libcr_ipc/chan.c +++ b/libcr_ipc/chan.c @@ -4,60 +4,22 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <alloca.h> /* for alloca() */ #include <string.h> /* for memcpy() */ #include <libcr/coroutine.h> /* for cid_t, cr_* */ #include <libmisc/assert.h> #include <libmisc/rand.h> +#define IMPLEMENTATION_FOR_LIBCR_IPC_CHAN_H YES #include <libcr_ipc/chan.h> -/* base channels **************************************************************/ - -struct cr_chan_waiter { +struct _cr_select_waiter { cid_t cid; - void *val_ptr; - void (*dequeue)(void *, size_t); - void *dequeue_arg1; - size_t dequeue_arg2; -}; -DLIST_DECLARE_NODE(_cr_chan_waiter_list, struct cr_chan_waiter); - -void cr_chan_dequeue(void *_ch, size_t) { - struct _cr_chan *ch = _ch; - dlist_pop_from_front(&ch->waiters); -} - -void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size) { - assert(ch); - assert(val_ptr); + struct _cr_select_arg_list_node *arg_vec; + size_t arg_cnt; - if (ch->waiters.front && ch->waiter_typ != self_typ) { /* non-blocking fast-path */ - /* Copy. */ - struct _cr_chan_waiter_list_node *front = ch->waiters.front; - if (self_typ == _CR_CHAN_SENDER) - memcpy(front->val.val_ptr, val_ptr, val_size); - else - memcpy(val_ptr, front->val.val_ptr, val_size); - cr_unpause(front->val.cid); - front->val.dequeue(front->val.dequeue_arg1, - front->val.dequeue_arg2); - cr_yield(); - } else { /* blocking slow-path */ - struct _cr_chan_waiter_list_node self = { .val = { - .cid = cr_getcid(), - .val_ptr = val_ptr, - .dequeue = cr_chan_dequeue, - .dequeue_arg1 = ch, - }}; - dlist_push_to_rear(&ch->waiters, &self); - ch->waiter_typ = self_typ; - cr_pause_and_yield(); - } -} - -/* select *********************************************************************/ + size_t ret; +}; enum cr_select_class { CR_SELECT_CLASS_DEFAULT, @@ -65,40 +27,26 @@ enum cr_select_class { CR_SELECT_CLASS_NONBLOCK, }; -struct cr_select_waiters { - size_t cnt; - struct cr_select_arg *args; - struct _cr_chan_waiter_list_node *nodes; -}; - -static inline enum cr_select_class cr_select_getclass(struct cr_select_arg arg) { - switch (arg.op) { +static inline enum cr_select_class cr_select_getclass(struct _cr_select_arg *arg) { + switch (arg->op) { case _CR_SELECT_OP_RECV: - if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_SENDER) + if (arg->ch->waiters.front && arg->ch->waiters.front->val.op == _CR_SELECT_OP_SEND) return CR_SELECT_CLASS_NONBLOCK; else return CR_SELECT_CLASS_BLOCKING; case _CR_SELECT_OP_SEND: - if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_RECVER) + if (arg->ch->waiters.front && arg->ch->waiters.front->val.op == _CR_SELECT_OP_RECV) return CR_SELECT_CLASS_NONBLOCK; else return CR_SELECT_CLASS_BLOCKING; case _CR_SELECT_OP_DEFAULT: return CR_SELECT_CLASS_DEFAULT; default: - assert_notreached("invalid arg.op"); + assert_notreached("invalid arg->op"); } } -void cr_select_dequeue(void *_waiters, size_t idx) { - struct cr_select_waiters *waiters = _waiters; - for (size_t i = 0; i < waiters->cnt; i++) - dlist_remove(&(waiters->args[i].ch->waiters), - &(waiters->nodes[i])); - waiters->cnt = idx; -} - -size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { +size_t cr_select_v(size_t arg_cnt, struct _cr_select_arg_list_node arg_vec[]) { size_t cnt_blocking = 0; size_t cnt_nonblock = 0; size_t cnt_default = 0; @@ -108,7 +56,7 @@ size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { cr_assert_in_coroutine(); for (size_t i = 0; i < arg_cnt; i++) { - switch (cr_select_getclass(arg_vec[i])) { + switch (cr_select_getclass(&arg_vec[i].val)) { case CR_SELECT_CLASS_BLOCKING: cnt_blocking++; break; @@ -122,46 +70,65 @@ size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { } if (cnt_nonblock) { - size_t choice = rand_uint63n(cnt_nonblock); - for (size_t i = 0, seen = 0; i < arg_cnt; i++) { - if (cr_select_getclass(arg_vec[i]) == CR_SELECT_CLASS_NONBLOCK) { - if (seen == choice) { - _cr_chan_xfer(arg_vec[i].op == _CR_SELECT_OP_RECV - ? _CR_CHAN_RECVER - : _CR_CHAN_SENDER, - arg_vec[i].ch, - arg_vec[i].val_ptr, - arg_vec[i].val_siz); - return i; - } + size_t choice_among_nonblock = rand_uint63n(cnt_nonblock); + size_t choice_among_all = arg_cnt; + for (size_t i = 0, seen = 0; i < choice_among_all; i++) { + if (cr_select_getclass(&arg_vec[i].val) == CR_SELECT_CLASS_NONBLOCK) { + if (seen == choice_among_nonblock) + choice_among_all = i; seen++; } } - assert_notreached("should have returned from inside for() loop"); + assert(choice_among_all < arg_cnt); + + struct _cr_select_arg *this = &arg_vec[choice_among_all].val; + assert(this->ch->waiters.front); + struct _cr_select_arg *other = &this->ch->waiters.front->val; + assert(this->val_siz == other->val_siz); + assert(this->ch == other->ch); + switch (this->op) { + case _CR_SELECT_OP_SEND: + assert(other->op == _CR_SELECT_OP_RECV); + memcpy(other->val_ptr, this->val_ptr, this->val_siz); + break; + case _CR_SELECT_OP_RECV: + assert(other->op == _CR_SELECT_OP_SEND); + memcpy(this->val_ptr, other->val_ptr, this->val_siz); + break; + case _CR_SELECT_OP_DEFAULT: + assert_notreached("_CR_SELECT_OP_DEFAULT is not CR_SELECT_CLASS_NONBLOCK"); + } + struct _cr_select_waiter *waiter = other->waiter; + for (size_t i = 0; i < waiter->arg_cnt; i++) { + waiter->arg_vec[i].val.ch->nwaiters--; + dlist_remove(&waiter->arg_vec[i].val.ch->waiters, &waiter->arg_vec[i]); + if (&waiter->arg_vec[i].val == other) + waiter->ret = i; + } + cr_unpause(waiter->cid); + cr_yield(); + return choice_among_all; } if (cnt_default) { for (size_t i = 0; i < arg_cnt; i++) - if (cr_select_getclass(arg_vec[i]) == CR_SELECT_CLASS_DEFAULT) + if (cr_select_getclass(&arg_vec[i].val) == CR_SELECT_CLASS_DEFAULT) return i; assert_notreached("should have returned from inside for() loop"); } - struct cr_select_waiters waiters = { - .cnt = arg_cnt, - .args = arg_vec, - .nodes = alloca(sizeof(struct cr_chan_waiter) * arg_cnt), + assert(cnt_blocking && cnt_blocking == arg_cnt); + + struct _cr_select_waiter waiter = { + .cid = cr_getcid(), + .arg_vec = arg_vec, + .arg_cnt = arg_cnt, }; for (size_t i = 0; i < arg_cnt; i++) { - waiters.nodes[i] = (struct _cr_chan_waiter_list_node){ .val = { - .cid = cr_getcid(), - .val_ptr = arg_vec[i].val_ptr, - .dequeue = cr_select_dequeue, - .dequeue_arg1 = &waiters, - .dequeue_arg2 = i, - }}; - dlist_push_to_rear(&arg_vec[i].ch->waiters, &waiters.nodes[i]); + arg_vec[i].val.waiter = &waiter; + arg_vec[i].val.ch->nwaiters++; + dlist_push_to_rear(&arg_vec[i].val.ch->waiters, &arg_vec[i]); } cr_pause_and_yield(); - return waiters.cnt; + return waiter.ret; } diff --git a/libcr_ipc/include/libcr_ipc/chan.h b/libcr_ipc/include/libcr_ipc/chan.h index 5a87643..c57979a 100644 --- a/libcr_ipc/include/libcr_ipc/chan.h +++ b/libcr_ipc/include/libcr_ipc/chan.h @@ -11,6 +11,7 @@ #include <stddef.h> /* for size_t */ #include <libmisc/linkedlist.h> /* for DLIST_DECLARE() */ +#include <libmisc/private.h> /* base channels **************************************************************/ @@ -44,12 +45,10 @@ * * void cr_chan_send(NAME##_t *ch, VAL_T val); */ -#define cr_chan_send(CH, VAL) do { \ - cr_assert_in_coroutine(); \ - typeof((CH)->val_typ[0]) _val_lvalue = VAL; \ - _cr_chan_xfer(_CR_CHAN_SENDER, &(CH)->core, \ - &_val_lvalue, sizeof(_val_lvalue)); \ -} while(0) +#define cr_chan_send(CH, VAL) do { \ + typeof((CH)->val_typ[0]) _val_lvalue = VAL; \ + (void)cr_select_l(CR_SELECT_SEND(CH, &_val_lvalue)); \ +} while (0) /** * cr_chan_recv(ch) reads and returns a value from ch. @@ -60,12 +59,10 @@ * * VAL_T cr_chan_recv(NAME##_T ch); */ -#define cr_chan_recv(CH) ({ \ - cr_assert_in_coroutine(); \ - typeof((CH)->val_typ[0]) _val_lvalue; \ - _cr_chan_xfer(_CR_CHAN_RECVER, &(CH)->core, \ - &_val_lvalue, sizeof(_val_lvalue)); \ - _val_lvalue; \ +#define cr_chan_recv(CH) ({ \ + typeof((CH)->val_typ[0]) _val_lvalue; \ + (void)cr_select_l(CR_SELECT_RECV(CH, &_val_lvalue)); \ + _val_lvalue; \ }) /** @@ -78,10 +75,10 @@ * * bool cr_chan_can_send(NAME##_t *ch); */ -#define cr_chan_can_send(CH) ({ \ - cr_assert_in_coroutine(); \ - (bool)((CH)->core.waiters.front && \ - (CH)->core.waiter_typ == _CR_CHAN_RECVER); \ +#define cr_chan_can_send(CH) ({ \ + cr_assert_in_coroutine(); \ + (bool)((CH)->core.waiters.front && \ + (CH)->core.waiters.front->val.op == _CR_SELECT_OP_RECV); \ }) /** @@ -94,78 +91,101 @@ * * bool cr_chan_can_recv(NAME##_t *ch); */ -#define cr_chan_can_recv(CH) ({ \ - cr_assert_in_coroutine(); \ - (bool)((CH)->core.waiters.front && \ - (CH)->core.waiter_typ == _CR_CHAN_SENDER); \ +#define cr_chan_can_recv(CH) ({ \ + cr_assert_in_coroutine(); \ + (bool)((CH)->core.waiters.front && \ + (CH)->core.waiters.front->val.op == _CR_SELECT_OP_SEND); \ }) +/** + * cr_chan_num_waiters(ch) returns the number of coroutines currently + * blocked on the channel. + * + * @runs_in coroutine + * @cr_pauses never + * @cr_yields never + * + * size_t cr_chan_num_waiters(NAME##_t *ch); + */ +#define cr_chan_num_waiters(CH) ({ \ + cr_assert_in_coroutine(); \ + ((CH)->core.nwaiters); \ +}) -enum _cr_chan_waiter_typ { - _CR_CHAN_SENDER, - _CR_CHAN_RECVER, -}; - -DLIST_DECLARE(_cr_chan_waiter_list); - +DLIST_DECLARE(_cr_select_arg_list); struct _cr_chan { - enum _cr_chan_waiter_typ waiter_typ; - struct _cr_chan_waiter_list waiters; + struct _cr_select_arg_list waiters; + size_t nwaiters; }; -void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size); - /* cr_select arguments ********************************************************/ /** * Do not populate cr_select_arg yourself; use the * CR_SELECT_{RECV,SEND,DEFAULT} macros. */ -struct cr_select_arg { +struct _cr_select_waiter; +struct _cr_select_arg { enum { _CR_SELECT_OP_RECV, _CR_SELECT_OP_SEND, _CR_SELECT_OP_DEFAULT, - } op; - struct _cr_chan *ch; - void *val_ptr; - size_t val_siz; + } op; + struct _cr_chan *ch; + void *val_ptr; + size_t val_siz; + BEGIN_PRIVATE(LIBCR_IPC_CHAN_H); + struct _cr_select_waiter *waiter; + END_PRIVATE(LIBCR_IPC_CHAN_H); }; +DLIST_DECLARE_NODE(_cr_select_arg_list, struct _cr_select_arg); +#define cr_select_arg _cr_select_arg_list_node -#define CR_SELECT_RECV(CH, VALP) ((struct cr_select_arg){ \ +#define CR_SELECT_RECV(CH, VALP) ((struct cr_select_arg){ .val = { \ .op = _CR_SELECT_OP_RECV, \ .ch = &((CH)->core), \ /* The _valp temporary variable is to get the compiler to check that \ * the types are compatible. */ \ .val_ptr = ({ typeof((CH)->val_typ[0]) *_valp = VALP; _valp; }), \ .val_siz = sizeof((CH)->val_typ[0]), \ -}) +}}) /* BUG: It's bogus that CR_SELECT_SEND takes VALP instead of VAL, but * since we need an address, taking VAL would introduce uncomfortable * questions about where VAL sits on the stack. */ -#define CR_SELECT_SEND(CH, VALP) ((struct cr_select_arg){ \ +#define CR_SELECT_SEND(CH, VALP) ((struct cr_select_arg){ .val = { \ .op = _CR_SELECT_OP_SEND, \ .ch = &((CH)->core), \ /* The _valp temporary variable is to get the compiler to check that \ * the types are compatible. */ \ .val_ptr = ({ typeof((CH)->val_typ[0]) *_valp = VALP; _valp; }), \ .val_siz = sizeof((CH)->val_typ[0]), \ -}) +}}) -#define CR_SELECT_DEFAULT ((struct cr_select_arg){ \ - .op = _CR_SELECT_OP_DEFAULT, \ -}) +#define CR_SELECT_DEFAULT ((struct cr_select_arg){ .val = { \ + .op = _CR_SELECT_OP_DEFAULT, \ +}}) /* cr_select_v(arg_cnt, arg_vec) **********************************************/ +/** + * @runs_in coroutine + * @cr_pauses maybe + * @cr_yields always + */ size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]); /* cr_select_l(arg1, arg2, arg3, ...) ******************************************/ -#define cr_select_l(...) ({ \ - struct cr_select_arg _cr_select_args[] = { __VA_ARGS__ }; \ - cr_select_v(sizeof(_cr_select_args)/sizeof(_cr_select_args[0])); \ +/** + * @runs_in coroutine + * @cr_pauses maybe + * @cr_yields always + */ +#define cr_select_l(...) ({ \ + struct cr_select_arg _cr_select_args[] = { __VA_ARGS__ }; \ + cr_select_v(sizeof(_cr_select_args)/sizeof(_cr_select_args[0]), \ + _cr_select_args); \ }) #endif /* _LIBCR_IPC_CHAN_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/rpc.h b/libcr_ipc/include/libcr_ipc/rpc.h index ecf48cf..bfa0a04 100644 --- a/libcr_ipc/include/libcr_ipc/rpc.h +++ b/libcr_ipc/include/libcr_ipc/rpc.h @@ -123,7 +123,7 @@ *((REQ)._resp) = RESP; \ cr_unpause(REQ._requester); \ cr_yield(); \ -} while(0) +} while (0) /* Background details *********************************************************/ diff --git a/libcr_ipc/tests/test_chan.c b/libcr_ipc/tests/test_chan.c index e5d9dc8..4788dd4 100644 --- a/libcr_ipc/tests/test_chan.c +++ b/libcr_ipc/tests/test_chan.c @@ -11,7 +11,7 @@ CR_CHAN_DECLARE(intchan, int); -COROUTINE cr_producer(void *_ch) { +COROUTINE producer_cr(void *_ch) { intchan_t *ch = _ch; cr_begin(); @@ -26,7 +26,7 @@ COROUTINE cr_producer(void *_ch) { cr_end(); } -COROUTINE cr_consumer(void *_ch) { +COROUTINE consumer_cr(void *_ch) { int x; intchan_t *ch = _ch; cr_begin(); @@ -42,8 +42,8 @@ COROUTINE cr_consumer(void *_ch) { int main() { intchan_t ch = {0}; - coroutine_add("producer", cr_producer, &ch); - coroutine_add("consumer", cr_consumer, &ch); + coroutine_add("producer", producer_cr, &ch); + coroutine_add("consumer", consumer_cr, &ch); coroutine_main(); return 0; } diff --git a/libcr_ipc/tests/test_mutex.c b/libcr_ipc/tests/test_mutex.c index 43714c9..d08315d 100644 --- a/libcr_ipc/tests/test_mutex.c +++ b/libcr_ipc/tests/test_mutex.c @@ -11,7 +11,7 @@ int counter = 0; -COROUTINE cr_worker(void *_mu) { +COROUTINE worker_cr(void *_mu) { cr_mutex_t *mu = _mu; cr_begin(); @@ -29,8 +29,8 @@ COROUTINE cr_worker(void *_mu) { int main() { cr_mutex_t mu = {}; - coroutine_add("a", cr_worker, &mu); - coroutine_add("b", cr_worker, &mu); + coroutine_add("a", worker_cr, &mu); + coroutine_add("b", worker_cr, &mu); coroutine_main(); test_assert(counter == 200); return 0; diff --git a/libcr_ipc/tests/test_rpc.c b/libcr_ipc/tests/test_rpc.c index 910b738..1461450 100644 --- a/libcr_ipc/tests/test_rpc.c +++ b/libcr_ipc/tests/test_rpc.c @@ -14,7 +14,7 @@ CR_RPC_DECLARE(intrpc, int, int); /* Test that the RPC is fair, have worker1 start waiting first, and * ensure that it gets the first request. */ -COROUTINE cr_caller(void *_ch) { +COROUTINE caller_cr(void *_ch) { intrpc_t *ch = _ch; cr_begin(); @@ -27,7 +27,7 @@ COROUTINE cr_caller(void *_ch) { cr_exit(); } -COROUTINE cr_worker1(void *_ch) { +COROUTINE worker1_cr(void *_ch) { intrpc_t *ch = _ch; cr_begin(); @@ -38,7 +38,7 @@ COROUTINE cr_worker1(void *_ch) { cr_exit(); } -COROUTINE cr_worker2(void *_ch) { +COROUTINE worker2_cr(void *_ch) { intrpc_t *ch = _ch; cr_begin(); @@ -51,9 +51,9 @@ COROUTINE cr_worker2(void *_ch) { int main() { intrpc_t ch = {0}; - coroutine_add("worker1", cr_worker1, &ch); - coroutine_add("caller", cr_caller, &ch); - coroutine_add("worker2", cr_worker2, &ch); + coroutine_add("worker1", worker1_cr, &ch); + coroutine_add("caller", caller_cr, &ch); + coroutine_add("worker2", worker2_cr, &ch); coroutine_main(); return 0; } diff --git a/libcr_ipc/tests/test_select.c b/libcr_ipc/tests/test_select.c index f0a71a3..9b5d117 100644 --- a/libcr_ipc/tests/test_select.c +++ b/libcr_ipc/tests/test_select.c @@ -14,7 +14,7 @@ CR_CHAN_DECLARE(intchan, int); intchan_t ch[10] = {0}; intchan_t fch = {0}; -COROUTINE cr_consumer(void *) { +COROUTINE consumer_cr(void *) { cr_begin(); struct cr_select_arg args[11]; @@ -53,15 +53,14 @@ COROUTINE cr_consumer(void *) { test_assert(ret_arg == 0); send = 890; - args[0] = CR_SELECT_SEND(&fch, &send); - args[1] = CR_SELECT_DEFAULT; - ret_arg = cr_select_v(2, args); + ret_arg = cr_select_l(CR_SELECT_SEND(&fch, &send), + CR_SELECT_DEFAULT); test_assert(ret_arg == 1); cr_end(); } -COROUTINE cr_producer(void *_n) { +COROUTINE producer_cr(void *_n) { int n = *(int *)_n; cr_begin(); @@ -70,7 +69,7 @@ COROUTINE cr_producer(void *_n) { cr_end(); } -COROUTINE cr_final(void *) { +COROUTINE final_cr(void *) { cr_begin(); int ret = cr_chan_recv(&fch); @@ -82,9 +81,9 @@ COROUTINE cr_final(void *) { int main() { for (int i = 0; i < 10; i++) - coroutine_add("producer", cr_producer, &i); - coroutine_add("consumer", cr_consumer, NULL); - coroutine_add("final", cr_final, NULL); + coroutine_add("producer", producer_cr, &i); + coroutine_add("consumer", consumer_cr, NULL); + coroutine_add("final", final_cr, NULL); coroutine_main(); return 0; } diff --git a/libcr_ipc/tests/test_sema.c b/libcr_ipc/tests/test_sema.c index e5b22a5..435c01a 100644 --- a/libcr_ipc/tests/test_sema.c +++ b/libcr_ipc/tests/test_sema.c @@ -13,7 +13,7 @@ int counter = 0; -COROUTINE cr_first(void *_sema) { +COROUTINE first_cr(void *_sema) { cr_sema_t *sema = _sema; cr_begin(); @@ -24,7 +24,7 @@ COROUTINE cr_first(void *_sema) { cr_exit(); } -COROUTINE cr_second(void *_sema) { +COROUTINE second_cr(void *_sema) { cr_sema_t *sema = _sema; cr_begin(); @@ -35,7 +35,7 @@ COROUTINE cr_second(void *_sema) { cr_exit(); } -COROUTINE cr_producer(void *_sema) { +COROUTINE producer_cr(void *_sema) { cr_sema_t *sema = _sema; cr_begin(); @@ -45,7 +45,7 @@ COROUTINE cr_producer(void *_sema) { cr_end(); } -COROUTINE cr_consumer(void *_sema) { +COROUTINE consumer_cr(void *_sema) { cr_sema_t *sema = _sema; cr_begin(); @@ -59,16 +59,16 @@ int main() { cr_sema_t sema = {}; printf("== test 1 =========================================\n"); - coroutine_add("first", cr_first, &sema); - coroutine_add("second", cr_second, &sema); + coroutine_add("first", first_cr, &sema); + coroutine_add("second", second_cr, &sema); coroutine_main(); test_assert(sema.cnt == 0); printf("== test 2 =========================================\n"); - coroutine_add("consumer", cr_consumer, &sema); - coroutine_add("producer", cr_producer, &sema); + coroutine_add("consumer", consumer_cr, &sema); + coroutine_add("producer", producer_cr, &sema); coroutine_main(); - coroutine_add("consumer", cr_consumer, &sema); + coroutine_add("consumer", consumer_cr, &sema); coroutine_main(); test_assert(sema.cnt == 0); |