mruby-task: prevent segfault when sleep is called from C function; fix #6642

when sleep() was called from within a C function (such as module.new's block
evaluation), the task scheduler would segfault while attempting to resume the
task. this occurred because C functions don't execute bytecode and thus their
callinfo has no valid program counter (pc). when the task tried to resume
execution, mrb_vm_exec() received a null pc, causing a segmentation fault.

the fix adds two safeguards in task.c:

1. C function boundary detection: before suspending a task for sleep, check
   if we're inside a C function by examining the cci (c call info) field.
   if cci > 0, fall back to blocking sleep via HAL instead of attempting
   cooperative context switch. this preserves sleep functionality without
   raising exceptions, though it blocks other tasks during the sleep period.

2. proc fallback in execute_task(): use the task's stored proc if the
   current callinfo's proc is null, ensuring mrb_vm_exec() always receives
   a valid proc pointer.

this approach prioritizes functionality over strict cooperative multitasking
semantics - tasks can still sleep inside C functions, but the sleep becomes
blocking. the alternative would be raising an exception like fiber does, but
that would break existing code unexpectedly.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-10-16 10:45:10 +09:00
parent 610ff67906
commit f8883178c5
+13 -1
View File
@@ -336,7 +336,8 @@ execute_task(mrb_state *mrb, mrb_task *t)
t->c.status = MRB_FIBER_RUNNING;
/* Save proc and PC to locals before calling mrb_vm_exec */
const struct RProc *proc = t->c.ci->proc;
/* Use current callinfo's proc if available, otherwise use task's stored proc */
const struct RProc *proc = t->c.ci->proc ? t->c.ci->proc : mrb_proc_ptr(t->proc);
const mrb_code *pc = t->c.ci->pc;
/* Set vmexec flag to prevent fiber_terminate from being called */
@@ -491,6 +492,17 @@ sleep_us_impl(mrb_state *mrb, mrb_int usec)
return;
}
/* Check for C function boundary - cannot do cooperative context switch */
mrb_callinfo *ci;
for (ci = mrb->c->ci; ci >= mrb->c->cibase; ci--) {
if (ci->cci > 0) {
/* Inside C function - fall back to blocking sleep without context switch */
mrb_hal_task_sleep_us(mrb, usec);
switching_ = FALSE;
return;
}
}
/* In task context - get current running task */
t = MRB2TASK(mrb);