From d1289ac8f97160a5c4e6cc20b8773c6064d079af Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 28 May 2026 07:27:37 +0900 Subject: [PATCH] vm.c: defer task switches during gc.iterating RETURN_IF_TASK_STOPPED used to bail out of mrb_vm_exec as soon as task.switching was set, even when the exec was called re-entrantly from inside a heap-walk callback (e.g. ObjectSpace.each_object via mrb_yield). Bailing in that situation only unwinds the inner exec, leaving the outer C iteration to keep calling the callback. The call-info stack drifts on each subsequent mrb_yield, and the program either trips the cibase assertion in mrb_vm_run or crashes in __longjmp. Hold off the switch while mrb->gc.iterating is set so the heap walk finishes intact; the switch then fires at the next OP boundary once the walk releases the flag. MRB_TASK_STOPPED is intentionally not deferred, since exiting promptly is still correct when the task itself is going away. Fixes #6862. Co-authored-by: Claude --- src/vm.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vm.c b/src/vm.c index b1de67637..fae3b929c 100644 --- a/src/vm.c +++ b/src/vm.c @@ -1588,8 +1588,16 @@ prepare_tagged_break(mrb_state *mrb, uint32_t tag, const mrb_callinfo *return_ci #define CALL_CODE_HOOKS() do { insn = BYTECODE_DECODER(*ci->pc); CODE_FETCH_HOOK(mrb, irep, ci->pc, regs); } while (0) #ifdef MRB_USE_TASK_SCHEDULER +/* Defer task switches while a C-level ObjectSpace walk holds gc.iterating + true. The walk runs callbacks (which may call back into mrb_vm_exec via + mrb_yield); returning early from an inner exec while the outer C + iteration is still active drifts the call-info stack and eventually + crashes (issue #6862). Switches resume at the next OP boundary after + the walk releases gc.iterating. A pending MRB_TASK_STOPPED is not + deferred -- the task is going away. */ #define RETURN_IF_TASK_STOPPED(mrb) do { \ - if ((mrb)->task.switching || (mrb)->c->status == MRB_TASK_STOPPED) \ + if (((mrb)->task.switching && !(mrb)->gc.iterating) || \ + (mrb)->c->status == MRB_TASK_STOPPED) \ return mrb_nil_value(); \ } while (0) #define TASK_STOP(mrb) do { \