From 3b2d5f202228b102c46a6a1f1434204973724ebb Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Thu, 22 Oct 2026 19:13:46 -0700 Subject: [PATCH] Node islands: give a nested entry its own scheduling state A call into Node from an async fiber runs in a context of its own on Node's stack while the calling fiber stays suspended in it, but runtime code that Node called back there still ran as that fiber: scr_current named it, and a fiber resumed from there saved its resumer in scr_loop_ctx and, under ASan, its fake stack in the calling fiber's slot. The calling fiber's next park then returned to a context that no longer existed, or under ASan resumed with a fake stack the nested context had destroyed (6870-node-island-server-fibers in dev mode, sanitized: SEGV in nh_keep_done). The nested entry now runs host callbacks as the loop's code does (no current fiber, the main exception cell and ALS slot) and puts the calling fiber's state and the loop context back when it returns. Co-Authored-By: Claude Opus 5.5 --- packages/runtime/src/scr_async.c | 48 ++++++++++++++++++++++++++ packages/runtime/src/scr_node_island.c | 2 ++ packages/runtime/src/scr_node_island.h | 5 +++ 3 files changed, 55 insertions(+) diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c index 921c73648..64a3712a3 100644 --- a/packages/runtime/src/scr_async.c +++ b/packages/runtime/src/scr_async.c @@ -1664,6 +1664,54 @@ static void scr_switch(ScrCtx *from, ScrCtx *to, ScrFiber *to_fiber) { #endif } +#ifdef SCR_NODE_ISLANDS +/* A nested entry into Node (scr_node_island.c) runs on Node's stack in a + * context of its own while the fiber that made the call stays suspended in + * it. Runtime code that Node calls back there runs as the loop's code does: + * no current fiber, the main exception cell and ALS slot. It may resume + * other fibers, which saves the resumer's context in scr_loop_ctx (and, + * under ASan, its fake stack in the main slot); those belong to whoever + * resumed the calling fiber, so they are saved here and put back when the + * nested entry returns, or the calling fiber's next park would return to a + * context that no longer exists. */ +typedef struct { + ScrFiber *current; + ScrAlsCtx **als; + ScrCtx loop_ctx; +#ifdef SCR_ASAN_FIBERS + void *main_fake; +#endif +} ScrNestedSave; + +void *scr_loop_nested_enter(void) { + ScrNestedSave *s = malloc(sizeof *s); + if (s == NULL) scr_oom(); + s->current = scr_current; + s->als = scr_als_active; + s->loop_ctx = scr_loop_ctx; +#ifdef SCR_ASAN_FIBERS + s->main_fake = scr_main_fake_stack; + scr_main_fake_stack = NULL; +#endif + scr_current = NULL; + scr_exc_swap_cell(NULL); + scr_als_active = &scr_als_main_slot; + return s; +} + +void scr_loop_nested_leave(void *save) { + ScrNestedSave *s = save; + scr_loop_ctx = s->loop_ctx; +#ifdef SCR_ASAN_FIBERS + scr_main_fake_stack = s->main_fake; +#endif + scr_current = s->current; + scr_exc_swap_cell(s->current ? &s->current->exc : NULL); + scr_als_active = s->als; + free(s); +} +#endif + static void scr_promise_settle_wake(ScrPromise *p); /* Copies src's settlement into a still-pending dst (payload RETAINED — diff --git a/packages/runtime/src/scr_node_island.c b/packages/runtime/src/scr_node_island.c index 1c6bfbea3..c297a3630 100644 --- a/packages/runtime/src/scr_node_island.c +++ b/packages/runtime/src/scr_node_island.c @@ -332,6 +332,7 @@ static void ni_on_node_stack_below_mark(void (*job)(void *), void *arg) { ni_nested_return = &caller; uintptr_t guard = scr_stack_guard; SCR_STACK_GUARD_SET(scr_stack_guard_for((uintptr_t)lo, hi - (uintptr_t)lo)); + void *loop_save = scr_loop_nested_enter(); #ifdef NI_ASAN void *caller_fake = NULL; __sanitizer_start_switch_fiber(&caller_fake, lo, hi - (uintptr_t)lo); @@ -342,6 +343,7 @@ static void ni_on_node_stack_below_mark(void (*job)(void *), void *arg) { size_t size; __sanitizer_finish_switch_fiber(caller_fake, &bottom, &size); #endif + scr_loop_nested_leave(loop_save); SCR_STACK_GUARD_SET(guard); } diff --git a/packages/runtime/src/scr_node_island.h b/packages/runtime/src/scr_node_island.h index 2241f3fc2..320328fde 100644 --- a/packages/runtime/src/scr_node_island.h +++ b/packages/runtime/src/scr_node_island.h @@ -39,6 +39,11 @@ void scr_loop_host(const struct ScrNodeBridge *bridge, double since_ms); * fiber; 0: none), and the test for Node's stack it uses. */ extern uintptr_t scr_node_stack_low; extern bool (*scr_node_stack_has)(uintptr_t sp); +/* Around a nested entry: the runtime's scheduling state of the suspended + * calling fiber, saved and put back (the SCR_NODE_ISLANDS build of + * scr_async.c). */ +void *scr_loop_nested_enter(void); +void scr_loop_nested_leave(void *save); bool scr_loop_is_hosted(void); void scr_loop_hosted_run(uint32_t kind, uint32_t token); void scr_loop_hosted_io_state(struct ScrNodeIoState *state);