summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-02-04 21:03:37 -0700
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-02-04 21:03:37 -0700
commit5c373cfe544df4ddba44adf3f6b60399340e4bd5 (patch)
treef96f5c57bbb042b3962bed49853621f6d344de04
parent65f2a6fb8797385e06e61e13bac7a33d27f2097d (diff)
parent57d4c9f154cacd5d0dcc2f45033d784b3e0b6734 (diff)
Merge branch 'lukeshu/9p-exit'
-rw-r--r--lib9p/include/lib9p/srv.h2
-rw-r--r--lib9p/srv.c15
-rwxr-xr-xlib9p/tests/runtest9
-rw-r--r--lib9p/tests/test_server/main.c105
-rw-r--r--libcr/coroutine.c105
-rw-r--r--libcr/include/libcr/coroutine.h20
-rw-r--r--libcr_ipc/CMakeLists.txt4
-rw-r--r--libcr_ipc/include/libcr_ipc/_linkedlist.h7
-rw-r--r--libcr_ipc/include/libcr_ipc/rpc.h164
-rw-r--r--libcr_ipc/tests/test_rpc.c59
-rw-r--r--libhw/host_net.c5
-rw-r--r--libmisc/include/libmisc/log.h16
12 files changed, 386 insertions, 125 deletions
diff --git a/lib9p/include/lib9p/srv.h b/lib9p/include/lib9p/srv.h
index 55cf5db..d45d9a4 100644
--- a/lib9p/include/lib9p/srv.h
+++ b/lib9p/include/lib9p/srv.h
@@ -100,6 +100,8 @@ struct lib9p_srv {
/* For internal use */
BEGIN_PRIVATE(LIB9P_SRV_H)
+ unsigned int readers;
+ unsigned int writers;
_lib9p_srv_reqch_t _reqch;
END_PRIVATE(LIB9P_SRV_H)
};
diff --git a/lib9p/srv.c b/lib9p/srv.c
index 7e6402e..10a8a96 100644
--- a/lib9p/srv.c
+++ b/lib9p/srv.c
@@ -200,6 +200,8 @@ static void handle_message(struct _lib9p_srv_req *ctx);
assert(srv->rootdir);
assert(listener);
+ srv->readers++;
+
uint32_t initial_rerror_overhead = rerror_overhead_for_version(0, buf);
for (;;) {
@@ -210,7 +212,11 @@ static void handle_message(struct _lib9p_srv_req *ctx);
};
if (!conn.fd) {
nonrespond_errorf("accept: error");
- continue;
+ srv->readers--;
+ if (srv->readers == 0)
+ while (srv->writers > 0)
+ _lib9p_srv_reqch_send_req(&srv->_reqch, NULL);
+ cr_exit();
}
struct _srv_sess sess = {
@@ -287,9 +293,16 @@ COROUTINE lib9p_srv_write_cr(void *_srv) {
assert(srv->rootdir);
cr_begin();
+ srv->writers++;
+
for (;;) {
/* Receive the request from the reader coroutine. ************/
rpc_handle = _lib9p_srv_reqch_recv_req(&srv->_reqch);
+ if (!rpc_handle.req) {
+ srv->writers--;
+ _lib9p_srv_reqch_send_resp(rpc_handle, 0);
+ cr_exit();
+ }
/* Deep-copy the request from the reader coroutine's
* stack to our stack. */
req = *rpc_handle.req;
diff --git a/lib9p/tests/runtest b/lib9p/tests/runtest
index 29d2089..d963c53 100755
--- a/lib9p/tests/runtest
+++ b/lib9p/tests/runtest
@@ -26,7 +26,8 @@ while [[ -d /proc/$server_pid && "$(readlink /proc/$server_pid/fd/4 2>/dev/null)
out=$("${client[@]}" ls -l '')
expect_lines \
'd-r-xr-xr-x M 0 root root 0 Oct 7 15:51 Documentation' \
- '--r--r--r-- M 0 root root 14 Oct 7 15:51 README.md'
+ '--r--r--r-- M 0 root root 14 Oct 7 15:51 README.md' \
+ '---w--w--w- M 0 root root 0 Oct 7 15:51 shutdown'
out=$("${client[@]}" ls -l 'Documentation/')
expect_lines \
@@ -43,3 +44,9 @@ expect_lines \
out=$("${client[@]}" stat 'Documentation/x')
expect_lines \
"'x' 'root' 'root' 'root' q (0000000000000009 1 ) m 0444 at 1728337905 mt 1728337904 l 4 t 0 d 0"
+
+out=$("${client[@]}" write 'shutdown' <<<1)
+expect_lines ''
+
+wait "$server_pid"
+trap - EXIT
diff --git a/lib9p/tests/test_server/main.c b/lib9p/tests/test_server/main.c
index 8e5011a..71c6cc2 100644
--- a/lib9p/tests/test_server/main.c
+++ b/lib9p/tests/test_server/main.c
@@ -24,7 +24,80 @@
#error config.h must define CONFIG_SRV9P_NUM_CONNS
#endif
-/* implementation *************************************************************/
+/* globals ********************************************************************/
+
+static implements_lib9p_srv_file *get_root(struct lib9p_srv_ctx *, struct lib9p_s);
+
+const char *hexdig = "0123456789abcdef";
+
+struct {
+ struct hostnet_tcp_listener listeners[CONFIG_SRV9P_NUM_CONNS];
+ struct lib9p_srv srv;
+} globals = {
+ .srv = (struct lib9p_srv){
+ .rootdir = get_root,
+ },
+};
+
+/* api ************************************************************************/
+
+struct api_file {
+ implements_lib9p_srv_file;
+
+ uint64_t pathnum;
+};
+
+static implements_lib9p_srv_file *api_clone(implements_lib9p_srv_file *self, struct lib9p_srv_ctx *) { return self; }
+static void api_free(implements_lib9p_srv_file *, struct lib9p_srv_ctx *) {}
+
+static uint32_t api_io(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, lib9p_o_t) { return 0; }
+static void api_wstat(implements_lib9p_srv_file *, struct lib9p_srv_ctx *ctx, struct lib9p_stat) { lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot wstat API file"); }
+static void api_remove(implements_lib9p_srv_file *, struct lib9p_srv_ctx *ctx) { lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot remove API file"); }
+
+static struct lib9p_stat api_stat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *) {
+ struct api_file *self = VCALL_SELF(struct api_file, implements_lib9p_srv_file, _self);
+ return (struct lib9p_stat){
+ .kern_type = 0,
+ .kern_dev = 0,
+ .file_qid = {
+ .type = LIB9P_QT_FILE,
+ .vers = 1,
+ .path = self->pathnum,
+ },
+ .file_mode = 0222,
+ .file_atime = 1728337905,
+ .file_mtime = 1728337904,
+ .file_size = 0,
+ .file_name = lib9p_str("shutdown"),
+ .file_owner_uid = lib9p_str("root"),
+ .file_owner_gid = lib9p_str("root"),
+ .file_last_modified_uid = lib9p_str("root"),
+ .file_extension = lib9p_str(NULL),
+ .file_owner_n_uid = 0,
+ .file_owner_n_gid = 0,
+ .file_last_modified_n_uid = 0,
+ };
+}
+static uint32_t api_pwrite(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, void *, uint32_t byte_count, uint64_t) {
+ if (byte_count == 0)
+ return 0;
+ for (int i = 0; i < CONFIG_SRV9P_NUM_CONNS; i++)
+ VCALL(&globals.listeners[i], close);
+ return byte_count;
+}
+
+static struct lib9p_srv_file_vtable api_file_vtable = {
+ .clone = api_clone,
+ .free = api_free,
+
+ .io = api_io,
+ .stat = api_stat,
+ .wstat = api_wstat,
+ .remove = api_remove,
+
+ .pread = NULL,
+ .pwrite = api_pwrite,
+};
/* file tree ******************************************************************/
@@ -74,6 +147,10 @@ static struct util9p_static_dir root = {
},
}),
STATIC_FILE("README.md", README_md),
+ &((struct api_file){
+ .vtable = &api_file_vtable,
+ .pathnum = __COUNTER__,
+ }),
NULL,
},
};
@@ -84,26 +161,17 @@ static implements_lib9p_srv_file *get_root(struct lib9p_srv_ctx *LM_UNUSED(ctx),
/* main ***********************************************************************/
-static COROUTINE read_cr(void *_srv) {
- struct lib9p_srv *srv = _srv;
- assert(srv);
-
+static COROUTINE read_cr(void *_i) {
+ int i = *((int *)_i);
cr_begin();
- struct hostnet_tcp_listener listener;
- hostnet_tcp_listener_init(&listener, 9000);
+ hostnet_tcp_listener_init(&globals.listeners[i], 9000);
- lib9p_srv_read_cr(srv, &listener);
+ lib9p_srv_read_cr(&globals.srv, &globals.listeners[i]);
cr_end();
}
-const char *hexdig = "0123456789abcdef";
-
-struct lib9p_srv srv = {
- .rootdir = get_root,
-};
-
static COROUTINE init_cr(void *) {
cr_begin();
@@ -111,13 +179,13 @@ static COROUTINE init_cr(void *) {
for (int i = 0; i < CONFIG_SRV9P_NUM_CONNS; i++) {
char name[] = {'r', 'e', 'a', 'd', '-', hexdig[i], '\0'};
- if (!coroutine_add(name, read_cr, &srv))
- error(1, 0, "coroutine_add(read_cr, &srv)");
+ if (!coroutine_add(name, read_cr, &i))
+ error(1, 0, "coroutine_add(read_cr, &i)");
}
for (int i = 0; i < 2*CONFIG_SRV9P_NUM_CONNS; i++) {
char name[] = {'w', 'r', 'i', 't', 'e', '-', hexdig[i], '\0'};
- if (!coroutine_add(name, lib9p_srv_write_cr, &srv))
- error(1, 0, "coroutine_add(lib9p_srv_write_cr, &srv)");
+ if (!coroutine_add(name, lib9p_srv_write_cr, &globals.srv))
+ error(1, 0, "coroutine_add(lib9p_srv_write_cr, &globals.srv)");
}
cr_exit();
@@ -126,4 +194,5 @@ static COROUTINE init_cr(void *) {
int main() {
coroutine_add("init", init_cr, NULL);
coroutine_main();
+ return 0;
}
diff --git a/libcr/coroutine.c b/libcr/coroutine.c
index 708e2ea..c6eeb43 100644
--- a/libcr/coroutine.c
+++ b/libcr/coroutine.c
@@ -387,23 +387,24 @@ static_assert(CONFIG_COROUTINE_NUM > 1);
/* types **********************************************************************/
-enum coroutine_state {
- CR_NONE = 0, /* this slot in the table is empty */
- CR_INITIALIZING, /* running, before cr_begin() */
- CR_RUNNING, /* running, after cr_begin() */
- CR_RUNNABLE, /* not running, but runnable */
- CR_PAUSED, /* not running, and not runnable */
-};
-
struct coroutine {
+ /* 1. state *************************************************/
volatile enum coroutine_state state;
- cr_plat_jmp_buf env;
+
+ /* 2. name **************************************************/
+ [[gnu::nonstring]] char name[CONFIG_COROUTINE_NAME_LEN];
+
+ /* 3. stack *************************************************/
+ /* stack_size *includes* CR_STACK_GUARD at each end. */
size_t stack_size;
+ /* stack is the bottom of the CR_STACK_GUARD at the bottom of the stack. */
void *stack;
#if CONFIG_COROUTINE_VALGRIND
unsigned stack_id;
#endif
- [[gnu::nonstring]] char name[CONFIG_COROUTINE_NAME_LEN];
+
+ /* 4. env ***************************************************/
+ cr_plat_jmp_buf env;
};
/* constants ******************************************************************/
@@ -476,11 +477,6 @@ static size_t coroutine_cnt = 0;
/* utility functions **********************************************************/
-static inline const char* coroutine_state_str(enum coroutine_state state) {
- assert(state < LM_ARRAY_LEN(coroutine_state_strs));
- return coroutine_state_strs[state];
-}
-
static inline void coroutine_ringbuf_push(cid_t val) {
coroutine_ringbuf.buf[coroutine_ringbuf.head++ % LM_ARRAY_LEN(coroutine_ringbuf.buf)] = val;
assert((coroutine_ringbuf.head % LM_ARRAY_LEN(coroutine_ringbuf.buf)) !=
@@ -576,37 +572,43 @@ cid_t coroutine_add_with_stack_size(size_t stack_size,
return 0;
debugf("...child=%zu", child);
+ /* 1. state *************************************************/
+ coroutine_table[child-1].state = CR_INITIALIZING;
+
+ /* 2. name **************************************************/
if (name)
strncpy(coroutine_table[child-1].name, name, sizeof(coroutine_table[child-1].name));
else
memset(coroutine_table[child-1].name, 0, sizeof(coroutine_table[child-1].name));
- coroutine_table[child-1].stack_size = stack_size;
- infof("allocing \"%s\" stack with size %zu", name, stack_size);
+ /* 3. stack *************************************************/
+ coroutine_table[child-1].stack_size = stack_size + 2*CR_STACK_GUARD_SIZE;
+ infof("allocing \"%s\" stack with size %zu+2*%zu=%zu",
+ name, stack_size, CR_STACK_GUARD_SIZE, coroutine_table[child-1].stack_size);
coroutine_table[child-1].stack =
- aligned_alloc(CR_PLAT_STACK_ALIGNMENT, stack_size);
- infof("...done");
+ aligned_alloc(CR_PLAT_STACK_ALIGNMENT, coroutine_table[child-1].stack_size);
+ infof("... done, stack is [%#zx,%#zx)",
+ coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE,
+ coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE + stack_size);
#if CONFIG_COROUTINE_MEASURE_STACK || CONFIG_COROUTINE_PROTECT_STACK
- for (size_t i = 0; i < stack_size; i++)
+ for (size_t i = 0; i < coroutine_table[child-1].stack_size; i++)
((uint8_t *)coroutine_table[child-1].stack)[i] =
stack_pattern[i%sizeof(stack_pattern)];
#endif
#if CONFIG_COROUTINE_VALGRIND
coroutine_table[child-1].stack_id = VALGRIND_STACK_REGISTER(
coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE,
- coroutine_table[child-1].stack + stack_size - CR_STACK_GUARD_SIZE);
+ coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE + stack_size);
#endif
+ /* 4. env ***************************************************/
coroutine_running = child;
- coroutine_table[child-1].state = CR_INITIALIZING;
coroutine_cnt++;
if (!cr_setjmp(&coroutine_add_env)) { /* point=a */
void *stack_base = coroutine_table[child-1].stack
+ + CR_STACK_GUARD_SIZE
#if CR_PLAT_STACK_GROWS_DOWNWARD
+ stack_size
- - CR_STACK_GUARD_SIZE
-#else
- + CR_STACK_GUARD_SIZE
#endif
;
debugf("...stack =%p", coroutine_table[child-1].stack);
@@ -785,20 +787,35 @@ void cr_assert_in_intrhandler(void) {
}
#endif
-/* cr_cid_info() **************************************************************/
-
-#if CONFIG_COROUTINE_MEASURE_STACK
+/* answering questions about coroutines ***************************************/
void cr_cid_info(cid_t cid, struct cr_cid_info *ret) {
- assert_cid(cid);
+ assert(cid > 0);
+ assert(cid <= CONFIG_COROUTINE_NUM);
assert(ret);
+ memset(ret, 0, sizeof(*ret));
+ if (coroutine_table[cid-1].state == CR_NONE)
+ return;
+ assert_cid(cid);
+
+ /* 1. state *************************************************/
+ ret->state = coroutine_table[cid-1].state;
+
+ /* 2. name **************************************************/
+ memcpy(ret->name, coroutine_table[cid-1].name, CONFIG_COROUTINE_NAME_LEN);
+
+ /* 3. stack *************************************************/
+#if CONFIG_COROUTINE_MEASURE_STACK
+ uint8_t *stack = (uint8_t *)coroutine_table[cid-1].stack;
+ uint8_t *stack_lo = stack + CR_STACK_GUARD_SIZE;
+ uint8_t *stack_hi = stack + coroutine_table[cid-1].stack_size - CR_STACK_GUARD_SIZE;
+
/* stack_cap */
- ret->stack_cap = coroutine_table[cid-1].stack_size - 2*CR_STACK_GUARD_SIZE;
+ ret->stack_cap = stack_hi - stack_lo;
/* stack_max */
ret->stack_max = ret->stack_cap;
- uint8_t *stack = (uint8_t *)coroutine_table[cid-1].stack;
for (;;) {
size_t i =
#if CR_PLAT_STACK_GROWS_DOWNWARD
@@ -807,8 +824,17 @@ void cr_cid_info(cid_t cid, struct cr_cid_info *ret) {
ret->stack_max - 1 - CR_STACK_GUARD_SIZE
#endif
;
- if (ret->stack_max == 0 ||
- stack[i] != stack_pattern[i%sizeof(stack_pattern)])
+ if (ret->stack_max == 0)
+ break;
+ assert(stack_lo <= &stack[i] && &stack[i] < stack_hi);
+#if CONFIG_COROUTINE_VALGRIND
+ VALGRIND_DISABLE_ERROR_REPORTING;
+#endif
+ uint8_t v = stack[i];
+#if CONFIG_COROUTINE_VALGRIND
+ VALGRIND_ENABLE_ERROR_REPORTING;
+#endif
+ if (v != stack_pattern[i%sizeof(stack_pattern)])
break;
ret->stack_max--;
}
@@ -822,12 +848,17 @@ void cr_cid_info(cid_t cid, struct cr_cid_info *ret) {
else
sp = cr_plat_setjmp_get_sp(&coroutine_table[cid-1].env);
assert(sp);
- uintptr_t sb = (uintptr_t)coroutine_table[cid-1].stack;
#if CR_PLAT_STACK_GROWS_DOWNWARD
- ret->stack_cur = (sb - CR_STACK_GUARD_SIZE) - sp;
+ uintptr_t sb = (uintptr_t)stack_hi;
+ ret->stack_cur = sb - sp;
#else
- ret->stack_cur = sp - (sb + CR_STACK_GUARD_SIZE);
+ uintptr_t sb = (uintptr_t)stack_lo;
+ ret->stack_cur = sp - sb;
#endif
+#endif /* CONFIG_COROUTINE_MEASURE_STACK */
}
-#endif /* CONFIG_COROUTINE_MEASURE_STACK */
+const char *coroutine_state_str(enum coroutine_state state) {
+ assert(state < LM_ARRAY_LEN(coroutine_state_strs));
+ return coroutine_state_strs[state];
+}
diff --git a/libcr/include/libcr/coroutine.h b/libcr/include/libcr/coroutine.h
index eb5828b..756352e 100644
--- a/libcr/include/libcr/coroutine.h
+++ b/libcr/include/libcr/coroutine.h
@@ -1,6 +1,6 @@
/* libcr/coroutine.h - Simple embeddable coroutine implementation
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -29,6 +29,8 @@
#include <stddef.h> /* for size_t */
#include <stdbool.h> /* for bool */
+#include "config.h"
+
/* typedefs *******************************************************************/
/**
@@ -187,13 +189,25 @@ void cr_assert_in_intrhandler(void);
/* answering questions about coroutines ***************************************/
-/* While the following are defined here unconditionally, the
- * implementations are #if'd on CONFIG_COROUTINE_MEASURE_STACK. */
+enum coroutine_state {
+ CR_NONE = 0, /* this slot in the table is empty */
+ CR_INITIALIZING, /* running, before cr_begin() */
+ CR_RUNNING, /* running, after cr_begin() */
+ CR_RUNNABLE, /* not running, but runnable */
+ CR_PAUSED, /* not running, and not runnable */
+};
+
+const char *coroutine_state_str(enum coroutine_state);
struct cr_cid_info {
+ enum coroutine_state state;
+ [[gnu::nonstring]] char name[CONFIG_COROUTINE_NAME_LEN];
+
+#if CONFIG_COROUTINE_MEASURE_STACK
size_t stack_cap;
size_t stack_max;
size_t stack_cur;
+#endif
};
void cr_cid_info(cid_t cid, struct cr_cid_info *ret);
diff --git a/libcr_ipc/CMakeLists.txt b/libcr_ipc/CMakeLists.txt
index 3ab56ab..05e6fa3 100644
--- a/libcr_ipc/CMakeLists.txt
+++ b/libcr_ipc/CMakeLists.txt
@@ -1,6 +1,6 @@
# libcr_ipc/CMakeLists.txt - TODO
#
-# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
add_library(libcr_ipc INTERFACE)
@@ -14,7 +14,7 @@ set(ipc_tests
#select
#mutex
#owned_mutex
- #rpc
+ rpc
#sema
)
if (ENABLE_TESTS)
diff --git a/libcr_ipc/include/libcr_ipc/_linkedlist.h b/libcr_ipc/include/libcr_ipc/_linkedlist.h
index 67bff24..543e058 100644
--- a/libcr_ipc/include/libcr_ipc/_linkedlist.h
+++ b/libcr_ipc/include/libcr_ipc/_linkedlist.h
@@ -1,6 +1,6 @@
/* libcr_ipc/_linkedlist.h - Common low-level linked lists for use in libcr_ipc
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -33,10 +33,9 @@ static inline void _cr_ipc_sll_push_to_rear(_cr_ipc_sll_root *root, _cr_ipc_sll_
node->rear = NULL;
if (root->rear)
root->rear->rear = node;
- else {
+ else
root->front = node;
- root->rear = node;
- }
+ root->rear = node;
}
static inline void _cr_ipc_sll_pop_from_front(_cr_ipc_sll_root *root) {
diff --git a/libcr_ipc/include/libcr_ipc/rpc.h b/libcr_ipc/include/libcr_ipc/rpc.h
index 80eee74..0ff8bbf 100644
--- a/libcr_ipc/include/libcr_ipc/rpc.h
+++ b/libcr_ipc/include/libcr_ipc/rpc.h
@@ -8,6 +8,7 @@
#define _LIBCR_IPC_RPC_H_
#include <stdbool.h> /* for bool */
+#include <string.h> /* for memcpy() */
#include <libcr/coroutine.h> /* for cid_t, cr_* */
@@ -34,6 +35,12 @@
* * _recv_req() and _send_resp().
* typedef ... NAME##_t;
*
+ * /**
+ * * A NAME##_req_t is handle that wraps a REQ_T and is used to return
+ * * the response RESP_T to the correct requester. `REQ_T req` is the
+ * * only public member.
+ * typedef struct { REQ_T req; ... } NAME##_req_t;
+ *
* methods:
*
* /**
@@ -89,71 +96,41 @@
#define CR_RPC_DECLARE(NAME, REQ_T, RESP_T) \
typedef struct { \
REQ_T req; \
- \
- RESP_T *_resp; \
+ \
+ RESP_T *_resp; /* where to write resp to */ \
cid_t _requester; \
} NAME##_req_t; \
- \
- struct _##NAME##_waiting_req { \
- _cr_ipc_sll_node; \
- REQ_T *req; \
- RESP_T *resp; \
- cid_t cid; \
- }; \
- \
- struct _##NAME##_waiting_resp { \
- _cr_ipc_sll_node; \
- cid_t cid; \
- }; \
- \
+ \
typedef struct { \
- _cr_ipc_sll_root waiting_reqs; \
- _cr_ipc_sll_root waiting_resps; \
+ struct _cr_rpc core; \
+ NAME##_req_t handle[0]; \
} NAME##_t; \
- \
+ \
static inline RESP_T NAME##_send_req(NAME##_t *ch, REQ_T req) { \
cr_assert_in_coroutine(); \
RESP_T resp; \
- struct _##NAME##_waiting_req self = { \
- .req = &req, \
- .resp = &resp, \
- .cid = cr_getcid(), \
- }; \
- _cr_ipc_sll_push_to_rear(&ch->waiting_reqs, &self); \
- if (ch->waiting_resps.front) { \
- cr_unpause(_cr_ipc_sll_node_cast(struct _##NAME##_waiting_resp, ch->waiting_resps.front)->cid); \
- _cr_ipc_sll_pop_from_front(&ch->waiting_resps); \
- } \
- cr_pause_and_yield(); \
+ _cr_rpc_send_req(&ch->core, \
+ &req, sizeof(req), \
+ &resp, sizeof(resp)); \
return resp; \
} \
- \
+ \
static inline NAME##_req_t NAME##_recv_req(NAME##_t *ch) { \
cr_assert_in_coroutine(); \
- if (!ch->waiting_reqs.front) { \
- struct _##NAME##_waiting_resp self = { \
- .cid = cr_getcid(), \
- }; \
- _cr_ipc_sll_push_to_rear(&ch->waiting_resps, &self); \
- cr_pause_and_yield(); \
- } \
- assert(ch->waiting_reqs.front); \
- struct _##NAME##_waiting_req *front_req = \
- _cr_ipc_sll_node_cast(struct _##NAME##_waiting_req, ch->waiting_reqs.front); \
- NAME##_req_t ret = { \
- .req = *(front_req->req), \
- ._resp = front_req->resp, \
- ._requester = front_req->cid, \
- }; \
- _cr_ipc_sll_pop_from_front(&ch->waiting_reqs); \
+ NAME##_req_t ret; \
+ _cr_rpc_recv_req(&ch->core, \
+ &ret.req, sizeof(ret.req), \
+ (void **)&ret._resp, \
+ &ret._requester); \
return ret; \
} \
- \
+ \
static inline bool NAME##_can_recv_req(NAME##_t *ch) { \
cr_assert_in_coroutine(); \
- return ch->waiting_reqs.front != NULL; \
+ return ch->core.waiters.front && \
+ ch->core.waiter_typ == _CR_RPC_REQUESTER; \
} \
- \
+ \
static inline void NAME##_send_resp(NAME##_req_t req, RESP_T resp) { \
cr_assert_in_coroutine(); \
*(req._resp) = resp; \
@@ -161,4 +138,93 @@
cr_yield(); \
}
+enum _cr_rpc_waiter_typ {
+ _CR_RPC_REQUESTER,
+ _CR_RPC_RESPONDER,
+};
+
+struct _cr_rpc_requester {
+ _cr_ipc_sll_node;
+ cid_t cid;
+ void *req_ptr; /* where to read req from */
+ void *resp_ptr; /* where to write resp to */
+};
+
+struct _cr_rpc_responder {
+ _cr_ipc_sll_node;
+ /* /* before enqueued | after dequeued */
+ /* /* -------------------+-------------------- */
+ cid_t cid; /* responder cid | requester cid */
+ void *ptr; /* where to write req | where to write resp */
+};
+
+struct _cr_rpc {
+ enum _cr_rpc_waiter_typ waiter_typ;
+ _cr_ipc_sll_root waiters;
+};
+
+static inline void _cr_rpc_send_req(struct _cr_rpc *ch,
+ void *req_ptr, size_t req_size,
+ void *resp_ptr, size_t resp_size)
+{
+ assert(ch);
+ assert(req_ptr);
+ assert(resp_ptr);
+
+ if (ch->waiters.front && ch->waiter_typ != _CR_RPC_REQUESTER) { /* fast-path (still blocks) */
+ struct _cr_rpc_responder *responder =
+ _cr_ipc_sll_node_cast(struct _cr_rpc_responder, ch->waiters.front);
+ _cr_ipc_sll_pop_from_front(&ch->waiters);
+ /* Copy the req to the responder's stack. */
+ memcpy(responder->ptr, req_ptr, req_size);
+ /* Notify the responder that we have done so. */
+ cr_unpause(responder->cid);
+ responder->cid = cr_getcid();
+ responder->ptr = resp_ptr;
+ /* Wait for the responder to set `*resp_ptr`. */
+ cr_pause_and_yield();
+ } else { /* blocking slow-path */
+ struct _cr_rpc_requester self = {
+ .cid = cr_getcid(),
+ .req_ptr = req_ptr,
+ .resp_ptr = resp_ptr,
+ };
+ _cr_ipc_sll_push_to_rear(&ch->waiters, &self);
+ /* Wait for a responder to both copy our req and sed
+ * `*resp_ptr`. */
+ cr_pause_and_yield();
+ }
+}
+
+static inline void _cr_rpc_recv_req(struct _cr_rpc *ch,
+ void *req_ptr, size_t req_size,
+ void **ret_resp_ptr,
+ cid_t *ret_requester)
+{
+ assert(ch);
+ assert(req_ptr);
+ assert(ret_resp_ptr);
+ assert(ret_requester);
+
+ if (ch->waiters.front && ch->waiter_typ != _CR_RPC_RESPONDER) { /* non-blocking fast-path */
+ struct _cr_rpc_requester *requester =
+ _cr_ipc_sll_node_cast(struct _cr_rpc_requester, ch->waiters.front);
+ _cr_ipc_sll_pop_from_front(&ch->waiters);
+
+ memcpy(req_ptr, requester->req_ptr, req_size);
+ *ret_requester = requester->cid;
+ *ret_resp_ptr = requester->resp_ptr;
+ } else { /* blocking slow-path */
+ struct _cr_rpc_responder self = {
+ .cid = cr_getcid(),
+ .ptr = req_ptr,
+ };
+ _cr_ipc_sll_push_to_rear(&ch->waiters, &self);
+ ch->waiter_typ = _CR_RPC_RESPONDER;
+ cr_pause_and_yield();
+ *ret_requester = self.cid;
+ *ret_resp_ptr = self.ptr;
+ }
+}
+
#endif /* _LIBCR_IPC_RPC_H_ */
diff --git a/libcr_ipc/tests/test_rpc.c b/libcr_ipc/tests/test_rpc.c
new file mode 100644
index 0000000..4aff5ca
--- /dev/null
+++ b/libcr_ipc/tests/test_rpc.c
@@ -0,0 +1,59 @@
+/* libcr_ipc/tests/test_rpc.c - Tests for <libcr_ipc/rpc.h>
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libcr/coroutine.h>
+#include <libcr_ipc/rpc.h>
+
+#include "test.h"
+
+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) {
+ intrpc_t *ch = _ch;
+ cr_begin();
+
+ int resp = intrpc_send_req(ch, 1);
+ test_assert(resp == 2);
+
+ resp = intrpc_send_req(ch, 3);
+ test_assert(resp == 4);
+
+ cr_exit();
+}
+
+COROUTINE cr_worker1(void *_ch) {
+ intrpc_t *ch = _ch;
+ cr_begin();
+
+ intrpc_req_t req = intrpc_recv_req(ch);
+ test_assert(req.req == 1);
+ intrpc_send_resp(req, 2);
+
+ cr_exit();
+}
+
+COROUTINE cr_worker2(void *_ch) {
+ intrpc_t *ch = _ch;
+ cr_begin();
+
+ intrpc_req_t req = intrpc_recv_req(ch);
+ test_assert(req.req == 3);
+ intrpc_send_resp(req, 4);
+
+ cr_exit();
+}
+
+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_main();
+ return 0;
+}
diff --git a/libhw/host_net.c b/libhw/host_net.c
index d5a5ad5..bf79480 100644
--- a/libhw/host_net.c
+++ b/libhw/host_net.c
@@ -190,6 +190,9 @@ static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_
if (RUN_PTHREAD(hostnet_pthread_accept, &args))
return NULL;
+ if (ret_connfd < 0)
+ return NULL;
+
listener->active_conn.vtable = &hostnet_tcp_conn_vtable;
listener->active_conn.fd = ret_connfd;
listener->active_conn.read_deadline_ns = 0;
@@ -203,7 +206,7 @@ static int hostnet_tcplist_close(implements_net_stream_listener *_listener) {
VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener);
assert(listener);
- return hostnet_map_negerrno(close(listener->fd) ? -errno : 0, OP_NONE);
+ return hostnet_map_negerrno(shutdown(listener->fd, SHUT_RDWR) ? -errno : 0, OP_NONE);
}
/* TCP read() *****************************************************************/
diff --git a/libmisc/include/libmisc/log.h b/libmisc/include/libmisc/log.h
index 121b0e1..79c0ab6 100644
--- a/libmisc/include/libmisc/log.h
+++ b/libmisc/include/libmisc/log.h
@@ -1,6 +1,6 @@
/* libmisc/log.h - stdio logging
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -12,25 +12,23 @@
#include <libmisc/macro.h>
#include <libmisc/_intercept.h>
-#ifndef LOG_NAME
- #error "each compilation unit that includes <libmisc/log.h> must define LOG_NAME"
-#endif
-
#ifdef NDEBUG
#define _LOG_NDEBUG 1
#else
#define _LOG_NDEBUG 0
#endif
+const char *const_byte_str(uint8_t b);
+
#define n_errorf(nam, fmt, ...) do { __lm_printf("error: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
#define n_infof(nam, fmt, ...) do { __lm_printf("info : " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
#define n_debugf(nam, fmt, ...) do { if (LM_CAT3_(CONFIG_, nam, _DEBUG) && !_LOG_NDEBUG) \
__lm_printf("debug: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
+#endif /* _LIBMISC_LOG_H_ */
+
+#if defined(LOG_NAME) && !defined(errorf)
#define errorf(fmt, ...) n_errorf(LOG_NAME, fmt, __VA_ARGS__)
#define infof(fmt, ...) n_infof(LOG_NAME, fmt, __VA_ARGS__)
#define debugf(fmt, ...) n_debugf(LOG_NAME, fmt, __VA_ARGS__)
-
-const char *const_byte_str(uint8_t b);
-
-#endif /* _LIBMISC_LOG_H_ */
+#endif