summaryrefslogtreecommitdiff
path: root/libcr_ipc
diff options
context:
space:
mode:
Diffstat (limited to 'libcr_ipc')
-rw-r--r--libcr_ipc/CMakeLists.txt2
-rw-r--r--libcr_ipc/include/libcr_ipc/rwmutex.h77
-rw-r--r--libcr_ipc/rwmutex.c103
-rw-r--r--libcr_ipc/tests/test_rwmutex.c88
4 files changed, 270 insertions, 0 deletions
diff --git a/libcr_ipc/CMakeLists.txt b/libcr_ipc/CMakeLists.txt
index 7d249d7..bd72f54 100644
--- a/libcr_ipc/CMakeLists.txt
+++ b/libcr_ipc/CMakeLists.txt
@@ -10,6 +10,7 @@ target_sources(libcr_ipc INTERFACE
chan.c
mutex.c
rpc.c
+ rwmutex.c
sema.c
)
target_link_libraries(libcr_ipc INTERFACE
@@ -21,6 +22,7 @@ set(ipc_tests
chan
mutex
rpc
+ rwmutex
select
sema
)
diff --git a/libcr_ipc/include/libcr_ipc/rwmutex.h b/libcr_ipc/include/libcr_ipc/rwmutex.h
new file mode 100644
index 0000000..924acce
--- /dev/null
+++ b/libcr_ipc/include/libcr_ipc/rwmutex.h
@@ -0,0 +1,77 @@
+/* libcr_ipc/rwmutex.h - Simple read/write mutexes for libcr
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBCR_IPC_RWMUTEX_H_
+#define _LIBCR_IPC_RWMUTEX_H_
+
+#include <stdbool.h>
+
+#include <libmisc/private.h>
+
+#include <libcr_ipc/_linkedlist_pub.h>
+
+/**
+ * A cr_rwmutex_t is a fair read/write mutex.
+ *
+ * A waiting writer blocks any new readers; this ensures that the
+ * writer is able to eventually get the lock.
+ *
+ * None of the methods have `_from_intrhandler` variants because (1)
+ * an interrupt handler can't block, so it shouldn't ever lock a mutex
+ * because that can block; and (2) if it can't lock a mutex in the
+ * first place, then it has no business unlocking one.
+ */
+typedef struct {
+ BEGIN_PRIVATE(LIBCR_IPC_RWMUTEX_H);
+ unsigned nreaders;
+ bool locked;
+ bool unpausing;
+ _cr_ipc_sll_root waiters;
+ END_PRIVATE(LIBCR_IPC_RWMUTEX_H);
+} cr_rwmutex_t;
+
+/**
+ * Lock the mutex for writing. Blocks if it is already locked.
+ *
+ * @runs_in coroutine
+ * @cr_pauses maybe
+ * @cr_yields maybe
+ */
+void cr_rwmutex_lock(cr_rwmutex_t *mu);
+
+/**
+ * Undo a single cr_rwmutex_lock() call. Unblocks either a single
+ * coroutine that is blocked on cr_rwmutex_lock() or arbitrarily many
+ * coroutines that are blocked on cr_rwmutex_rlock().
+ *
+ * @runs_in coroutine
+ * @cr_pauses never
+ * @cr_yields never
+ */
+void cr_rwmutex_unlock(cr_rwmutex_t *mu);
+
+/**
+ * Lock the mutex for reading. Blocks if it is already locked for
+ * writing.
+ *
+ * @runs_in coroutine
+ * @cr_pauses maybe
+ * @cr_yields maybe
+ */
+void cr_rwmutex_rlock(cr_rwmutex_t *mu);
+
+/**
+ * Undo a single cr_rwmutext_rock() call. If the reader count is
+ * reduced to zero, unblocks a single coroutine that is blocked on
+ * cr_rwmutex_lock().
+ *
+ * @runs_in coroutine
+ * @cr_pauses never
+ * @cr_yields never
+ */
+void cr_rwmutex_runlock(cr_rwmutex_t *mu);
+
+#endif /* _LIBCR_IPC_RWMUTEX_H_ */
diff --git a/libcr_ipc/rwmutex.c b/libcr_ipc/rwmutex.c
new file mode 100644
index 0000000..04016d6
--- /dev/null
+++ b/libcr_ipc/rwmutex.c
@@ -0,0 +1,103 @@
+/* libcr_ipc/rwmutex.c - Simple read/write mutexes for libcr
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libcr/coroutine.h> /* for cid_t, cr_* */
+
+#define IMPLEMENTATION_FOR_LIBCR_IPC_RWMUTEX_H YES
+#include <libcr_ipc/rwmutex.h>
+
+#include "_linkedlist.h"
+
+struct cr_rwmutex_waiter {
+ cr_ipc_sll_node;
+ bool is_reader;
+ cid_t cid;
+};
+
+void cr_rwmutex_lock(cr_rwmutex_t *mu) {
+ assert(mu);
+ cr_assert_in_coroutine();
+
+ struct cr_rwmutex_waiter self = {
+ .is_reader = false,
+ .cid = cr_getcid(),
+ };
+ cr_ipc_sll_push_to_rear(&mu->waiters, &self);
+ if (mu->waiters.front != &self.cr_ipc_sll_node || mu->locked)
+ cr_pause_and_yield();
+ assert(mu->waiters.front == &self.cr_ipc_sll_node);
+
+ /* We now hold the lock (and are mu->waiters.front). */
+ cr_ipc_sll_pop_from_front(&mu->waiters);
+ assert(mu->nreaders == 0);
+ mu->locked = true;
+ mu->unpausing = false;
+}
+
+void cr_rwmutex_rlock(cr_rwmutex_t *mu) {
+ assert(mu);
+ cr_assert_in_coroutine();
+
+ struct cr_rwmutex_waiter self = {
+ .is_reader = true,
+ .cid = cr_getcid(),
+ };
+ cr_ipc_sll_push_to_rear(&mu->waiters, &self);
+ if (mu->waiters.front != &self.cr_ipc_sll_node || (mu->locked && mu->nreaders == 0))
+ cr_pause_and_yield();
+ assert(mu->waiters.front == &self.cr_ipc_sll_node);
+
+ /* We now hold the lock (and are mu->waiters.front). */
+ cr_ipc_sll_pop_from_front(&mu->waiters);
+ mu->nreaders++;
+ mu->locked = true;
+ struct cr_rwmutex_waiter *waiter = mu->waiters.front
+ ? cr_ipc_sll_node_cast(struct cr_rwmutex_waiter, mu->waiters.front)
+ : NULL;
+ if (waiter && waiter->is_reader) {
+ assert(mu->unpausing);
+ cr_unpause(waiter->cid);
+ } else {
+ mu->unpausing = false;
+ }
+}
+
+void cr_rwmutex_unlock(cr_rwmutex_t *mu) {
+ assert(mu);
+ cr_assert_in_coroutine();
+
+ assert(mu->locked);
+ assert(mu->nreaders == 0);
+ assert(!mu->unpausing);
+ if (mu->waiters.front) {
+ struct cr_rwmutex_waiter *waiter =
+ cr_ipc_sll_node_cast(struct cr_rwmutex_waiter, mu->waiters.front);
+ mu->unpausing = true;
+ cr_unpause(waiter->cid);
+ } else {
+ mu->locked = false;
+ }
+}
+
+void cr_rwmutex_runlock(cr_rwmutex_t *mu) {
+ assert(mu);
+ cr_assert_in_coroutine();
+
+ assert(mu->locked);
+ assert(mu->nreaders > 0);
+ mu->nreaders--;
+ if (mu->nreaders == 0 && !mu->unpausing) {
+ if (mu->waiters.front) {
+ struct cr_rwmutex_waiter *waiter =
+ cr_ipc_sll_node_cast(struct cr_rwmutex_waiter, mu->waiters.front);
+ assert(!waiter->is_reader);
+ mu->unpausing = true;
+ cr_unpause(waiter->cid);
+ } else {
+ mu->locked = false;
+ }
+ }
+}
diff --git a/libcr_ipc/tests/test_rwmutex.c b/libcr_ipc/tests/test_rwmutex.c
new file mode 100644
index 0000000..77e8c7c
--- /dev/null
+++ b/libcr_ipc/tests/test_rwmutex.c
@@ -0,0 +1,88 @@
+/* libcr_ipc/tests/test_rwmutex.c - Tests for <libcr_ipc/rwmutex.h>
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <string.h>
+
+#include <libcr/coroutine.h>
+#include <libcr_ipc/rwmutex.h>
+
+#include "test.h"
+
+cr_rwmutex_t mu = {};
+char out[10] = {0};
+size_t len = 0;
+
+COROUTINE cr1_reader(void *_mu) {
+ cr_rwmutex_t *mu = _mu;
+ cr_begin();
+
+ cr_rwmutex_rlock(mu);
+ out[len++] = 'r';
+ cr_yield();
+ cr_rwmutex_runlock(mu);
+
+ cr_end();
+}
+
+COROUTINE cr1_writer(void *_mu) {
+ cr_rwmutex_t *mu = _mu;
+ cr_begin();
+
+ cr_rwmutex_lock(mu);
+ out[len++] = 'w';
+ cr_yield();
+ cr_rwmutex_unlock(mu);
+
+ cr_end();
+}
+
+COROUTINE cr2_waiter(void *_ch) {
+ char ch = *(char *)_ch;
+ cr_begin();
+
+ cr_rwmutex_rlock(&mu);
+ out[len++] = ch;
+ cr_rwmutex_runlock(&mu);
+
+ cr_end();
+}
+
+COROUTINE cr2_init(void *) {
+ cr_begin();
+
+ char ch;
+ cr_rwmutex_lock(&mu);
+ ch = 'a'; coroutine_add("wait-a", cr2_waiter, &ch);
+ ch = 'b'; coroutine_add("wait-b", cr2_waiter, &ch);
+ cr_yield();
+ ch = 'c'; coroutine_add("wait-c", cr2_waiter, &ch);
+ cr_rwmutex_unlock(&mu);
+
+ cr_end();
+}
+
+int main() {
+ printf("== test 1 =========================================\n");
+ coroutine_add("r1", cr1_reader, &mu);
+ coroutine_add("r2", cr1_reader, &mu);
+ coroutine_add("w", cr1_writer, &mu);
+ coroutine_add("r3", cr1_reader, &mu);
+ coroutine_add("r4", cr1_reader, &mu);
+ coroutine_main();
+ test_assert(len == 5);
+ test_assert(strcmp(out, "rrwrr") == 0);
+
+ printf("== test 2 =========================================\n");
+ mu = (cr_rwmutex_t){};
+ len = 0;
+ memset(out, 0, sizeof(out));
+ coroutine_add("init", cr2_init, NULL);
+ coroutine_main();
+ test_assert(len == 3);
+ test_assert(strcmp(out, "abc") == 0);
+
+ return 0;
+}