From 6461af9ccab9325108150251d0bd4d560f5c272f Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 9 Oct 2025 12:52:14 +0900 Subject: [PATCH] mruby-task: support Task.pass from root context Enable Task.pass to work from root context by implementing mini-scheduler iteration. When called from root context, Task.pass now runs one task iteration, allowing cooperative multitasking without Task.run. This matches PicoRuby behavior. Co-authored-by: Claude --- mrbgems/mruby-task/src/task.c | 162 ++++++++++++++++++++++++++++++---- 1 file changed, 145 insertions(+), 17 deletions(-) diff --git a/mrbgems/mruby-task/src/task.c b/mrbgems/mruby-task/src/task.c index f4f4856ef..6f70aaf08 100644 --- a/mrbgems/mruby-task/src/task.c +++ b/mrbgems/mruby-task/src/task.c @@ -454,24 +454,19 @@ mrb_tasks_run(mrb_state *mrb) static void sleep_us_impl(mrb_state *mrb, mrb_int usec) { - mrb_task *t = q_ready_; /* Current running task */ + mrb_task *t; - if (!t) { - /* Not in task context - use blocking sleep with retry on interruption */ -#ifdef __unix__ - struct timespec req, rem; - req.tv_sec = usec / 1000000; - req.tv_nsec = (usec % 1000000) * 1000; - /* nanosleep automatically retries on EINTR with SA_RESTART */ - while (nanosleep(&req, &rem) == -1) { - req = rem; /* Continue with remaining time if interrupted */ - } -#elif defined(_WIN32) - Sleep((DWORD)(usec / 1000)); -#endif + /* Check if we're in a task context */ + if (mrb->c == mrb->root_c) { + /* Not in task context - just advance the time */ + uint32_t sleep_ticks = ((usec / 1000) / MRB_TICK_UNIT); + tick_ += sleep_ticks; return; } + /* In task context - get current running task */ + t = MRB2TASK(mrb); + mrb_task_disable_irq(); /* Remove from ready queue */ @@ -983,12 +978,145 @@ mrb_task_s_list(mrb_state *mrb, mrb_value self) return ary; } +/* + * Run one task iteration - helper for Task.pass from root context + */ +static void +task_run_one_iteration(mrb_state *mrb) +{ + mrb_task *t = q_ready_; + struct mrb_context *prev_c; + mrb_callinfo *prev_ci; + uint8_t prev_cci; + + /* No ready task - check if we can wake up sleeping tasks */ + if (!t) { + if (q_waiting_) { + /* Advance time to wake up next sleeping task */ + mrb_task *curr = q_waiting_; + uint32_t next_wakeup = UINT32_MAX; + + /* Find earliest wakeup time */ + while (curr) { + if (curr->reason == MRB_TASKREASON_SLEEP && curr->wakeup_tick < next_wakeup) { + next_wakeup = curr->wakeup_tick; + } + curr = curr->next; + } + + /* Advance time to that point */ + if (next_wakeup != UINT32_MAX) { + tick_ = next_wakeup; + wakeup_tick_ = next_wakeup; + mrb_tick(mrb); /* Wake up tasks */ + t = q_ready_; /* Check if we now have ready tasks */ + } + } + + if (!t) { + return; /* Still no ready tasks */ + } + } + + /* Skip terminated tasks */ + if (t->status == MRB_TASKSTATUS_DORMANT || t->c.status == MRB_FIBER_TERMINATED) { + mrb_task_disable_irq(); + q_delete_task(mrb, t); + if (t->status != MRB_TASKSTATUS_DORMANT) { + t->status = MRB_TASKSTATUS_DORMANT; + q_insert_task(mrb, t); + } + mrb_task_enable_irq(); + return; + } + + /* Set task as running */ + t->status = MRB_TASKSTATUS_RUNNING; + t->timeslice = MRB_TIMESLICE_TICK_COUNT; + + /* Switch to task context */ + prev_c = mrb->c; + prev_ci = prev_c->ci; + prev_cci = prev_c->ci->cci; + t->c.prev = mrb->c; + mrb->c = &t->c; + + /* If task hasn't started yet, pop dummy callinfo */ + if (!t->started) { + t->c.ci--; + t->started = 1; + } + + /* Clear switching flag */ + switching_ = FALSE; + + /* Set status to RUNNING */ + t->c.status = MRB_FIBER_RUNNING; + + /* Save proc and PC to locals */ + const struct RProc *proc = t->c.ci->proc; + const mrb_code *pc = t->c.ci->pc; + + /* Set vmexec flag */ + t->c.vmexec = TRUE; + + /* Execute task */ + t->result = mrb_vm_exec(mrb, proc, pc); + + /* Clear vmexec flag */ + t->c.vmexec = FALSE; + + /* Clear switching flag */ + switching_ = FALSE; + + /* Restore context */ + mrb->c = prev_c; + t->c.prev = NULL; + prev_c->ci = prev_ci; + prev_ci->cci = prev_cci; + + /* Handle task termination */ + if (t->c.status == MRB_FIBER_TERMINATED) { + switching_ = FALSE; + mrb_task_disable_irq(); + q_delete_task(mrb, t); + t->status = MRB_TASKSTATUS_DORMANT; + q_insert_task(mrb, t); + task_count_update(mrb, MRB_TASKSTATUS_RUNNING, MRB_TASKSTATUS_DORMANT); + mrb_task_enable_irq(); + + /* Wake up tasks waiting on join */ + mrb_task *curr = q_waiting_; + while (curr != NULL) { + mrb_task *next = curr->next; + if (curr->reason == MRB_TASKREASON_JOIN && curr->join == t) { + mrb_task_disable_irq(); + q_delete_task(mrb, curr); + curr->status = MRB_TASKSTATUS_READY; + curr->reason = MRB_TASKREASON_NONE; + curr->join = NULL; + q_insert_task(mrb, curr); + task_count_update(mrb, MRB_TASKSTATUS_WAITING, MRB_TASKSTATUS_READY); + mrb_task_enable_irq(); + } + curr = next; + } + } + else if (t->status == MRB_TASKSTATUS_RUNNING) { + /* Task yielded but still running */ + t->status = MRB_TASKSTATUS_READY; + } +} + static mrb_value mrb_task_s_pass(mrb_state *mrb, mrb_value self) { - /* Only yield if we're in a task context */ - if (mrb->c != mrb->root_c) { - /* Trigger context switch to yield to other tasks */ + if (mrb->c == mrb->root_c) { + /* Called from root context - run one task iteration */ + task_run_one_iteration(mrb); + } + else { + /* In task context - trigger context switch */ switching_ = TRUE; }