summaryrefslogtreecommitdiff
path: root/libcr_ipc/chan.c
diff options
context:
space:
mode:
Diffstat (limited to 'libcr_ipc/chan.c')
-rw-r--r--libcr_ipc/chan.c134
1 files changed, 134 insertions, 0 deletions
diff --git a/libcr_ipc/chan.c b/libcr_ipc/chan.c
new file mode 100644
index 0000000..b7ecfc8
--- /dev/null
+++ b/libcr_ipc/chan.c
@@ -0,0 +1,134 @@
+/* libcr_ipc/chan.c - Simple channels for libcr
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#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>
+
+struct _cr_select_waiter {
+ cid_t cid;
+ struct _cr_select_arg_list_node *arg_vec;
+ size_t arg_cnt;
+
+ size_t ret;
+};
+
+enum cr_select_class {
+ CR_SELECT_CLASS_DEFAULT,
+ CR_SELECT_CLASS_BLOCKING,
+ CR_SELECT_CLASS_NONBLOCK,
+};
+
+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->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->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");
+ }
+}
+
+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;
+
+ assert(arg_cnt);
+ assert(arg_vec);
+ cr_assert_in_coroutine();
+
+ for (size_t i = 0; i < arg_cnt; i++) {
+ switch (cr_select_getclass(&arg_vec[i].val)) {
+ case CR_SELECT_CLASS_BLOCKING:
+ cnt_blocking++;
+ break;
+ case CR_SELECT_CLASS_NONBLOCK:
+ cnt_nonblock++;
+ break;
+ case CR_SELECT_CLASS_DEFAULT:
+ cnt_default++;
+ break;
+ }
+ }
+
+ if (cnt_nonblock) {
+ 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(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].val) == CR_SELECT_CLASS_DEFAULT)
+ return i;
+ assert_notreached("should have returned from inside for() loop");
+ }
+
+ 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++) {
+ 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 waiter.ret;
+}