Merge pull request #6871 from hasumikin/fix/execute_task

Fix execute_task() so unhandled task exceptions become task results
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-05-29 07:05:36 +09:00
committed by GitHub
4 changed files with 60 additions and 2 deletions
+1
View File
@@ -275,6 +275,7 @@ typedef struct mrb_task_state {
struct mrb_task *main_task; /* Main task wrapper for root context */
uint8_t scheduler_lock; /* Lock counter for synchronous execution */
mrb_bool loop_running; /* Active mrb_task_run loop flag */
mrb_bool exception_as_result; /* Return unhandled task exceptions as values */
} mrb_task_state;
#endif
+42 -2
View File
@@ -324,6 +324,27 @@ task_change_state(mrb_state *mrb, mrb_task *t, uint8_t new_status)
mrb_task_enable_irq();
}
typedef struct execute_task_vm_args {
mrb_task *t;
const struct RProc *proc;
const mrb_code *pc;
} execute_task_vm_args;
static mrb_value
execute_task_vm(mrb_state *mrb, void *ud)
{
execute_task_vm_args *args = (execute_task_vm_args*)ud;
mrb->task.exception_as_result = TRUE;
args->t->result = mrb_vm_exec(mrb, args->proc, args->pc);
if (mrb->exc) {
args->t->result = mrb_obj_value(mrb->exc);
mrb->exc = NULL;
}
mrb->task.exception_as_result = FALSE;
return args->t->result;
}
/* Execute a single task - core task execution logic */
static void
execute_task(mrb_state *mrb, mrb_task *t)
@@ -358,8 +379,13 @@ execute_task(mrb_state *mrb, mrb_task *t)
/* Set vmexec flag to prevent fiber_terminate from being called */
t->c.vmexec = TRUE;
/* Execute task - PC is saved in ci->pc from previous run */
t->result = mrb_vm_exec(mrb, proc, pc);
/* Execute task - PC is saved in ci->pc from previous run.
Unhandled task exceptions are converted to the task result by
mrb_vm_exec() in task mode, so the scheduler protect frame stays intact. */
execute_task_vm_args args = { t, proc, pc };
mrb_bool error = FALSE;
t->result = mrb_protect_error(mrb, execute_task_vm, &args, &error);
mrb->task.exception_as_result = FALSE;
/* Clear vmexec flag */
t->c.vmexec = FALSE;
@@ -373,6 +399,18 @@ execute_task(mrb_state *mrb, mrb_task *t)
prev_c->ci = prev_ci;
prev_ci->cci = prev_cci;
/* If an abnormal path inside mrb_vm_exec() bypassed
exception_as_result and unwound via MRB_THROW (e.g. a
CINFO_SKIP frame), mrb_protect_error caught it and stored the
exception object in t->result. Force the task to terminate
cleanly so the scheduler keeps running instead of aborting -
re-raising into the scheduler would abort in pattern 1, where
no outer jmpbuf exists. The exception remains observable via
mrb_task_value() / Task#value. */
if (error) {
t->c.status = MRB_TASK_STOPPED;
}
/* Handle task termination */
if (t->c.status == MRB_TASK_STOPPED) {
switching_ = FALSE;
@@ -1534,6 +1572,7 @@ mrb_mruby_task_gem_init(mrb_state *mrb)
mrb->task.main_task = NULL;
mrb->task.scheduler_lock = 0;
mrb->task.loop_running = FALSE;
mrb->task.exception_as_result = FALSE;
task_class = mrb_define_class_id(mrb, MRB_SYM(Task), mrb->object_class);
MRB_SET_INSTANCE_TT(task_class, MRB_TT_DATA);
@@ -1565,6 +1604,7 @@ mrb_mruby_task_gem_init(mrb_state *mrb)
mrb_define_method_id(mrb, task_class, MRB_SYM(resume), mrb_task_resume, MRB_ARGS_NONE());
mrb_define_method_id(mrb, task_class, MRB_SYM(terminate), mrb_task_terminate, MRB_ARGS_NONE());
mrb_define_method_id(mrb, task_class, MRB_SYM(join), mrb_task_join, MRB_ARGS_NONE());
mrb_define_method_id(mrb, task_class, MRB_SYM(value), mrb_task_value, MRB_ARGS_NONE());
/* Kernel methods (module functions like CRuby)
* Note: sleep and usleep override mruby-sleep's implementation to be task-aware
+14
View File
@@ -194,3 +194,17 @@ assert("Task.run inside Task.run is a noop") do
Task.run
end
end
assert("Task#value returns exception object for unhandled task errors") do
child = nil
Task.new do
child = Task.new { raise "boom" }
end
Task.run
result = child.value
assert_kind_of RuntimeError, result
assert_equal "boom", result.message
end
+3
View File
@@ -1635,9 +1635,11 @@ task_across_c_boundary(mrb_state *mrb)
if (mrb->c->status != MRB_TASK_STOPPED) \
mrb->c->status = MRB_TASK_STOPPED; \
} while (0)
#define TASK_RETURN_EXCEPTION_AS_VALUE(mrb) ((mrb)->task.exception_as_result)
#else
#define RETURN_IF_TASK_STOPPED(mrb)
#define TASK_STOP(mrb)
#define TASK_RETURN_EXCEPTION_AS_VALUE(mrb) FALSE
#endif
/**
@@ -2691,6 +2693,7 @@ RETRY_TRY_BLOCK:
fiber_terminate(mrb, c, ci);
if (mrb_unlikely(!c->vmexec)) goto L_RAISE;
mrb->jmp = prev_jmp;
if (TASK_RETURN_EXCEPTION_AS_VALUE(mrb)) return mrb_obj_value(mrb->exc);
if (!prev_jmp) return mrb_obj_value(mrb->exc);
MRB_THROW(prev_jmp);
}