From 0a21eef938f37e7d22a9c77354738fde472875ab Mon Sep 17 00:00:00 2001 From: HASUMI Hitoshi Date: Fri, 9 Jan 2026 14:22:25 +0900 Subject: [PATCH 1/6] Fix mruby-task for PicoRuby Integration With this PR, I can remove the original task.c in picoruby/picoruby and future development will be much easier. ## Add ### General - C API functions exported with MRB_API for external integration: - mrb_execute_proc_synchronously() for synchronous proc execution - Task control APIs (mrb_create_task, mrb_suspend_task, mrb_resume_task, mrb_terminate_task, mrb_stop_task, mrb_task_value, mrb_task_status) - Task context management APIs for picoruby-sandbox (mrb_task_init_context, mrb_task_reset_context, mrb_task_proc_set) - Task.tick class method to get current tick count - Comprehensive C API documentation with WASM integration examples ### For PicoRuby.wasm - WASM/Emscripten support: Disable SIGALRM timer when __EMSCRIPTEN__ is defined, as JavaScript handles tick calls via setInterval - Scheduler lock mechanism to prevent asynchronous task operations during synchronous execution (scheduler_lock counter in mrb_task_state) - mrb_task_run_once() for single-step execution (event loop integration) ## Fix ### task.c - Replace MRB_FIBER_TERMINATED with MRB_TASK_STOPPED just for clarity - Allow suspending DORMANT and WAITING tasks in mrb_task_suspend (See comment in the source) - Task context initialization by removing dummy callinfo push/pop *NOTE* With the dummy callinfo code that I deleted, IRB in PicoRuby ended SEGV. If that code is mandatory, we need to discuss how to solve my problem. ### vm.c - Handle MRB_TASK_CREATED status in VM's NORMAL_RETURN phase to properly stop tasks ---- These changes are necessary to make PicoRuby work. Nevertheless, even with this patch, MicroRuby for Raspberry Pi Pico 2 is still unstable. I would like to merge this PR anyway to make development easier by involving the PicoRuby community. --- include/mruby.h | 1 + mrbgems/hal-posix-task/src/task_hal.c | 30 +- mrbgems/mruby-task/README.md | 147 +++++++++ mrbgems/mruby-task/include/task.h | 39 ++- mrbgems/mruby-task/src/task.c | 420 ++++++++++++++++++++++++-- src/vm.c | 9 + 6 files changed, 616 insertions(+), 30 deletions(-) diff --git a/include/mruby.h b/include/mruby.h index bfaf5d6ee1..542215541b 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -256,6 +256,7 @@ typedef struct mrb_task_state { volatile uint32_t wakeup_tick; /* Next wakeup tick */ volatile mrb_bool switching; /* Context switch pending flag */ struct mrb_task *main_task; /* Main task wrapper for root context */ + uint8_t scheduler_lock; /* Lock counter for synchronous execution */ } mrb_task_state; #endif diff --git a/mrbgems/hal-posix-task/src/task_hal.c b/mrbgems/hal-posix-task/src/task_hal.c index 81be9361c6..87b81adbcd 100644 --- a/mrbgems/hal-posix-task/src/task_hal.c +++ b/mrbgems/hal-posix-task/src/task_hal.c @@ -7,12 +7,18 @@ ** and sigprocmask() for interrupt protection. ** ** Supported platforms: Linux, macOS, BSD, Unix +** +** Note: When compiled for Emscripten/WASM, the SIGALRM timer is disabled +** because JavaScript handles tick calls via setInterval. Using both would +** cause tick_ to increment twice as fast, making sleep wake up early. */ #include #include "task_hal.h" +#ifndef __EMSCRIPTEN__ #include #include +#endif #include #include #include @@ -22,6 +28,7 @@ #define NSEC_PER_SEC 1000000000ULL #define USEC_PER_MSEC 1000ULL +#ifndef __EMSCRIPTEN__ /* Multi-VM support */ static mrb_state *vm_list[MRB_TASK_MAX_VMS]; static volatile sig_atomic_t vm_count = 0; @@ -40,6 +47,7 @@ sigalrm_handler(int sig) } } } +#endif /* __EMSCRIPTEN__ */ /* * HAL Interface Implementation @@ -48,10 +56,7 @@ sigalrm_handler(int sig) void mrb_hal_task_init(mrb_state *mrb) { - struct sigaction sa; - struct itimerval timer; int i; - int vm_index = -1; /* Initialize task state */ for (i = 0; i < 4; i++) { @@ -61,6 +66,12 @@ mrb_hal_task_init(mrb_state *mrb) mrb->task.wakeup_tick = UINT32_MAX; mrb->task.switching = FALSE; +#ifndef __EMSCRIPTEN__ + /* POSIX: Set up SIGALRM timer for tick handling */ + struct sigaction sa; + struct itimerval timer; + int vm_index = -1; + /* Block SIGALRM during registration to avoid race */ sigemptyset(&alarm_mask); sigaddset(&alarm_mask, SIGALRM); @@ -104,18 +115,26 @@ mrb_hal_task_init(mrb_state *mrb) /* Unblock SIGALRM */ sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL); +#else + /* WASM: JavaScript handles tick calls via setInterval, no timer needed here */ + (void)i; +#endif } void mrb_task_enable_irq(void) { +#ifndef __EMSCRIPTEN__ sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL); +#endif } void mrb_task_disable_irq(void) { +#ifndef __EMSCRIPTEN__ sigprocmask(SIG_BLOCK, &alarm_mask, NULL); +#endif } void @@ -180,6 +199,7 @@ mrb_hal_task_sleep_us(mrb_state *mrb, mrb_int usec) void mrb_hal_task_final(mrb_state *mrb) { +#ifndef __EMSCRIPTEN__ struct itimerval timer; int i, j; @@ -210,6 +230,10 @@ mrb_hal_task_final(mrb_state *mrb) /* Unblock SIGALRM */ sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL); +#else + /* WASM: No timer cleanup needed */ + (void)mrb; +#endif } /* diff --git a/mrbgems/mruby-task/README.md b/mrbgems/mruby-task/README.md index df5b34d880..c261795743 100644 --- a/mrbgems/mruby-task/README.md +++ b/mrbgems/mruby-task/README.md @@ -125,6 +125,15 @@ Task.run Task.run # Run scheduler until tasks complete ``` +- **`Task.tick`**: Returns the current tick count in milliseconds. This is the elapsed time since the scheduler started, measured in tick units. + + ```ruby + start_tick = Task.tick + do_work + elapsed = Task.tick - start_tick + puts "Work took #{elapsed} ms" + ``` + ### Task Instance Methods - **`#status`**: Returns the task status as a symbol (`:DORMANT`, `:READY`, `:RUNNING`, `:WAITING`, `:SUSPENDED`). @@ -261,6 +270,7 @@ The task scheduler uses a Hardware Abstraction Layer (HAL) to support different - Uses `sigprocmask()` for interrupt protection - Uses `SA_RESTART` to prevent `EINTR` on system calls - Supports multiple VMs per process +- **WASM/Emscripten support**: When compiled with Emscripten (`__EMSCRIPTEN__` defined), the SIGALRM timer is automatically disabled. JavaScript handles tick calls via `setInterval`, preventing double-increment of the tick counter **hal-win-task** - For Windows @@ -425,6 +435,134 @@ void mrb_hal_myplatform_task_gem_final(mrb_state *mrb) See `hal-posix-task` and `hal-win-task` source code for complete reference implementations. +## C API + +The task scheduler provides a C API for integrating with C code and embedding environments. All exported functions are marked with `MRB_API` for external linkage. + +### Core Scheduler API + +```c +/* Tick handler - called by timer interrupt */ +MRB_API void mrb_tick(mrb_state *mrb); + +/* Main scheduler loop - blocks until all tasks complete */ +MRB_API mrb_value mrb_task_run(mrb_state *mrb); + +/* Single-step task execution for event loop integration */ +MRB_API mrb_value mrb_task_run_once(mrb_state *mrb); +``` + +**`mrb_task_run_once()`** executes one ready task and returns. This is designed for WASM/JavaScript event loop integration where the scheduler should yield control back to the browser between task executions. + +### Task Creation API + +```c +/* Create a task from a proc */ +MRB_API mrb_value mrb_create_task(mrb_state *mrb, struct RProc *proc, + mrb_value name, mrb_value priority, + mrb_value top_self); +``` + +Creates a new task from a `RProc` object. The `name` should be a String or `mrb_nil_value()`, `priority` should be an Integer (0-255) or `mrb_nil_value()` for default priority (128), and `top_self` sets the task's self object (or `mrb_nil_value()` to use default). + +### Task Control API + +```c +/* Suspend a task - prevents it from running until resumed */ +MRB_API void mrb_suspend_task(mrb_state *mrb, mrb_value task); + +/* Resume a suspended task - moves it back to ready/waiting queue */ +MRB_API void mrb_resume_task(mrb_state *mrb, mrb_value task); + +/* Terminate a task immediately - moves to dormant state */ +MRB_API void mrb_terminate_task(mrb_state *mrb, mrb_value task); + +/* Stop a task - marks as stopped without moving to dormant */ +MRB_API mrb_bool mrb_stop_task(mrb_state *mrb, mrb_value task); + +/* Get task result value */ +MRB_API mrb_value mrb_task_value(mrb_state *mrb, mrb_value task); + +/* Get task status symbol */ +MRB_API mrb_value mrb_task_status(mrb_state *mrb, mrb_value task); +``` + +**Note**: These functions raise `E_RUNTIME_ERROR` if called during synchronous execution (when `scheduler_lock > 0`). + +### Synchronous Execution API + +```c +/* Execute a proc synchronously without context switching */ +MRB_API mrb_value mrb_execute_proc_synchronously(mrb_state *mrb, + mrb_value proc, + mrb_int argc, + const mrb_value *argv); +``` + +This function creates a temporary task, executes it to completion, and returns the result. During execution, the scheduler is locked (`scheduler_lock++`), preventing any asynchronous task operations. This is designed for **picoruby-wasm** to execute Ruby code synchronously from JavaScript without triggering task switches. + +**Key characteristics**: + +- Blocks until the proc completes execution +- No context switching occurs during execution +- Other tasks cannot be created, suspended, or resumed while locked +- Temporary task is automatically freed after execution +- If the proc raises an exception, it's returned as the result + +### Task Context Management API + +```c +/* Initialize task context with a new proc */ +MRB_API void mrb_task_init_context(mrb_state *mrb, mrb_value task, + struct RProc *proc); + +/* Reset task context to initial state */ +MRB_API void mrb_task_reset_context(mrb_state *mrb, mrb_value task); + +/* Set proc for task (without full reinitialization) */ +MRB_API void mrb_task_proc_set(mrb_state *mrb, mrb_value task, + struct RProc *proc); +``` + +These functions are designed for **picoruby-sandbox** to reuse task objects for multiple executions without reallocating memory. `mrb_task_init_context()` fully reinitializes the context, while `mrb_task_proc_set()` only updates the proc pointer. + +### Example: Event Loop Integration (WASM) + +```c +/* JavaScript calls this function periodically via setInterval */ +void js_tick_callback(void) { + mrb_tick(mrb); +} + +/* Main loop - called from JavaScript event loop */ +mrb_value js_run_task_once(void) { + return mrb_task_run_once(mrb); +} + +/* Execute Ruby code synchronously from JavaScript */ +mrb_value js_eval_sync(const char *code) { + struct RProc *proc = mrb_generate_code(mrb, code); + return mrb_execute_proc_synchronously(mrb, mrb_obj_value(proc), 0, NULL); +} +``` + +### Example: Task Creation from C + +```c +/* Create a background task */ +static mrb_value my_background_proc(mrb_state *mrb, mrb_value self) { + /* Task code here */ + return mrb_nil_value(); +} + +void create_background_task(mrb_state *mrb) { + struct RProc *proc = mrb_proc_new_cfunc(mrb, my_background_proc); + mrb_value name = mrb_str_new_cstr(mrb, "background"); + mrb_value priority = mrb_fixnum_value(128); + mrb_value task = mrb_create_task(mrb, proc, name, priority, mrb_nil_value()); +} +``` + ## Examples ### Basic Multitasking @@ -604,6 +742,15 @@ When a task is in WAITING state, the reason indicates why: 6. Requeue task to READY (if still running) or appropriate queue 7. Repeat from step 1 +### Scheduler Lock + +The scheduler includes a lock counter (`mrb->task.scheduler_lock`) that prevents asynchronous task operations during synchronous execution: + +- When `scheduler_lock > 0`, asynchronous APIs (`mrb_create_task`, `mrb_suspend_task`, `mrb_resume_task`) raise `E_RUNTIME_ERROR` +- This ensures that synchronous execution (via `mrb_execute_proc_synchronously`) completes without interference +- The lock is incremented when entering synchronous execution and decremented when exiting +- Maximum lock depth is 254 to prevent overflow + ## Future Enhancements Planned features not yet implemented: diff --git a/mrbgems/mruby-task/include/task.h b/mrbgems/mruby-task/include/task.h index 28e8661652..25154ba1be 100644 --- a/mrbgems/mruby-task/include/task.h +++ b/mrbgems/mruby-task/include/task.h @@ -101,10 +101,43 @@ void mrb_task_disable_irq(void); void mrb_task_hal_idle_cpu(mrb_state *mrb); /* - * Core task scheduler API + * GC integration */ -void mrb_tick(mrb_state *mrb); -mrb_value mrb_task_run(mrb_state *mrb); void mrb_task_mark_all(mrb_state *mrb); +/* + * Core task scheduler API + */ +MRB_API void mrb_tick(mrb_state *mrb); +MRB_API mrb_value mrb_task_run(mrb_state *mrb); +MRB_API mrb_value mrb_task_run_once(mrb_state *mrb); + +/* + * Task creation API + */ +MRB_API mrb_value mrb_create_task(mrb_state *mrb, struct RProc *proc, mrb_value name, mrb_value priority, mrb_value top_self); + +/* + * Synchronous execution API (for picoruby-wasm) + */ +MRB_API mrb_value mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc, mrb_int argc, const mrb_value *argv); + +/* + * Task control API + * Note: mrb_task_run is the main scheduler loop (for picoruby-sandbox and picoruby-wasm) + */ +MRB_API void mrb_suspend_task(mrb_state *mrb, mrb_value task); +MRB_API void mrb_resume_task(mrb_state *mrb, mrb_value task); +MRB_API void mrb_terminate_task(mrb_state *mrb, mrb_value task); +MRB_API mrb_bool mrb_stop_task(mrb_state *mrb, mrb_value task); +MRB_API mrb_value mrb_task_value(mrb_state *mrb, mrb_value task); +MRB_API mrb_value mrb_task_status(mrb_state *mrb, mrb_value self); + +/* + * Task context management API (for picoruby-sandbox) + */ +MRB_API void mrb_task_init_context(mrb_state *mrb, mrb_value task, struct RProc *proc); +MRB_API void mrb_task_reset_context(mrb_state *mrb, mrb_value task); +MRB_API void mrb_task_proc_set(mrb_state *mrb, mrb_value task, struct RProc *proc); + #endif /* MRUBY_TASK_H */ diff --git a/mrbgems/mruby-task/src/task.c b/mrbgems/mruby-task/src/task.c index fb550633d2..c60c291d8e 100644 --- a/mrbgems/mruby-task/src/task.c +++ b/mrbgems/mruby-task/src/task.c @@ -264,8 +264,6 @@ task_init_context(mrb_state *mrb, mrb_task *t, const struct RProc *proc) mrb_vm_ci_proc_set(ci, proc); ci->stack = c->stbase; ci->pc = proc->body.irep->iseq; /* Initialize PC to start of bytecode */ - ci[1] = ci[0]; - c->ci++; /* Push dummy callinfo */ c->status = MRB_TASK_CREATED; } @@ -324,17 +322,9 @@ execute_task(mrb_state *mrb, mrb_task *t) t->c.prev = mrb->c; mrb->c = &t->c; - /* If task hasn't started yet, pop dummy callinfo */ - if (t->c.status == MRB_FIBER_CREATED) { - t->c.ci--; - } - /* Clear switching flag */ switching_ = FALSE; - /* Set status to RUNNING so VM can transition it properly */ - 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; const mrb_code *pc = t->c.ci->pc; @@ -363,7 +353,7 @@ execute_task(mrb_state *mrb, mrb_task *t) prev_ci->cci = prev_cci; /* Handle task termination */ - if (t->c.status == MRB_FIBER_TERMINATED) { + if (t->c.status == MRB_TASK_STOPPED) { switching_ = FALSE; mrb_task_disable_irq(); q_delete_task(mrb, t); @@ -381,7 +371,7 @@ execute_task(mrb_state *mrb, mrb_task *t) } /* Tick handler - called by timer interrupt */ -void +MRB_API void mrb_tick(mrb_state *mrb) { mrb_task *t; @@ -429,7 +419,7 @@ mrb_tick(mrb_state *mrb) } /* Main scheduler loop */ -mrb_value +MRB_API mrb_value mrb_task_run(mrb_state *mrb) { mrb_task *t; @@ -449,7 +439,7 @@ mrb_task_run(mrb_state *mrb) } /* Safety check - don't execute terminated tasks */ - if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_FIBER_TERMINATED) { + if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_TASK_STOPPED) { /* Task is terminated but still in queue - remove it */ mrb_task_disable_irq(); q_delete_task(mrb, t); @@ -478,6 +468,46 @@ mrb_task_run(mrb_state *mrb) return mrb_nil_value(); } +/* Single-step task execution for WASM event loop integration */ +MRB_API mrb_value +mrb_task_run_once(mrb_state *mrb) +{ + mrb_task *t = q_ready_; + + /* No task ready */ + if (!t) { + return mrb_nil_value(); + } + + /* Safety check - don't execute terminated tasks */ + if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_TASK_STOPPED) { + /* Task is terminated but still in queue - remove it */ + mrb_task_disable_irq(); + q_delete_task(mrb, t); + if (t->status != MRB_TASK_STATUS_DORMANT) { + t->status = MRB_TASK_STATUS_DORMANT; + q_insert_task(mrb, t); + } + mrb_task_enable_irq(); + return mrb_true_value(); + } + + /* Execute task using core logic */ + execute_task(mrb, t); + + /* Move to end of ready queue if still ready (round-robin) */ + if (t->status == MRB_TASK_STATUS_READY) { + task_change_state(mrb, t, MRB_TASK_STATUS_READY); + } + + /* Run incremental GC if active */ + if (mrb->gc.state != MRB_GC_STATE_ROOT) { + mrb_incremental_gc(mrb); + } + + return mrb_true_value(); +} + /* * Sleep operations */ @@ -745,7 +775,7 @@ task_run_one_iteration(mrb_state *mrb) } /* Skip terminated tasks */ - if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_FIBER_TERMINATED) { + if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_TASK_STOPPED) { mrb_task_disable_irq(); q_delete_task(mrb, t); if (t->status != MRB_TASK_STATUS_DORMANT) { @@ -839,7 +869,7 @@ mrb_task_s_get(mrb_state *mrb, mrb_value self) { mrb_value name; - mrb_get_args(mrb, "o", &name); + mrb_get_args(mrb, "S", &name); /* Search all queues for task with matching name */ for (int i = 0; i < MRB_NUM_TASK_QUEUE; i++) { @@ -859,7 +889,7 @@ mrb_task_s_get(mrb_state *mrb, mrb_value self) * Task instance methods */ -static mrb_value +MRB_API mrb_value mrb_task_status(mrb_state *mrb, mrb_value self) { mrb_task *t; @@ -1002,15 +1032,23 @@ mrb_task_suspend(mrb_state *mrb, mrb_value self) TASK_GET_PTR_OR_RAISE(t, self); - /* Can only suspend ready or running tasks */ - if (t->status != MRB_TASK_STATUS_READY && t->status != MRB_TASK_STATUS_RUNNING) { - return self; - } + /* + * WAITING task should also be suspended: + * Suspend trigger possible happens when it is sleeping (WAITING). + * As in, DORMANT task should also be suspended: + * IRB in PicoRuby suspends a DORMANT task to use it again. + */ + + /* + * Determine if context switch is needed BEFORE changing state. + * Context switch is needed when suspending a RUNNING task or + * the current ready task. + */ + mrb_bool need_switch = (t == q_ready_ || t->status == MRB_TASK_STATUS_RUNNING); task_change_state(mrb, t, MRB_TASK_STATUS_SUSPENDED); - /* If suspending self, trigger context switch */ - if (t == q_ready_ || t->status == MRB_TASK_STATUS_RUNNING) { + if (need_switch) { switching_ = TRUE; } @@ -1112,6 +1150,338 @@ mrb_task_join(mrb_state *mrb, mrb_value self) return t->state.result; } +/* + * Synchronous execution + */ + +/* Execute a proc synchronously without context switching + * + * This function creates a temporary task, executes it to completion, + * and returns the result. The scheduler_lock prevents any asynchronous + * task operations during execution. + */ +MRB_API mrb_value +mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc_val, mrb_int argc, const mrb_value *argv) +{ + struct RProc *proc = mrb_proc_ptr(proc_val); + + /* 1. Lock scheduler and save context */ + mrb_assert(mrb->task.scheduler_lock < 254); /* Prevent overflow */ + mrb->task.scheduler_lock++; + struct mrb_context *original_c = mrb->c; + + /* 2. Create a temporary task */ + mrb_task *t = task_alloc(mrb); + t->priority = 0; /* Highest priority */ + t->status = MRB_TASK_STATUS_DORMANT; + t->reason = MRB_TASK_REASON_NONE; + t->name = mrb_str_new_lit(mrb, "(sync)"); + + /* Initialize task context */ + task_init_context(mrb, t, proc); + + /* Create wrapper object (not registered with GC as we'll free it manually) */ + struct RClass *task_class = mrb_class_get(mrb, "Task"); + mrb_value task_obj = mrb_obj_value(mrb_data_object_alloc(mrb, task_class, t, &mrb_task_type)); + t->self = task_obj; + + /* Arguments are currently unused in this implementation */ + (void)argc; + (void)argv; + + /* 3. Move task from DORMANT to READY */ + mrb_task_disable_irq(); + t->status = MRB_TASK_STATUS_READY; + q_insert_task(mrb, t); + mrb_task_enable_irq(); + + /* 4. Execute the task in a dedicated loop (no context switching) */ + t->status = MRB_TASK_STATUS_RUNNING; + mrb->c = &t->c; + + while (t->c.status != MRB_TASK_STOPPED) { + t->state.result = mrb_vm_exec(mrb, mrb->c->ci->proc, mrb->c->ci->pc); + } + + /* If there's an unhandled exception after VM stops, save it as result */ + if (mrb->exc) { + t->state.result = mrb_obj_value(mrb->exc); + } + + /* 5. Get result and clean up */ + mrb_value result = t->state.result; + if (mrb_obj_ptr(result) == mrb->exc) { + mrb->exc = NULL; /* Clear exception */ + } + + /* 6. Free the temporary task's resources */ + mrb_task_disable_irq(); + q_delete_task(mrb, t); + mrb_task_enable_irq(); + + /* Free context resources directly (bypass GC since we own this task) */ + if (t->c.stbase) { + mrb_free(mrb, t->c.stbase); + t->c.stbase = NULL; + } + if (t->c.cibase) { + mrb_free(mrb, t->c.cibase); + t->c.cibase = NULL; + } + mrb_free(mrb, t); + + /* 7. Restore context and unlock */ + mrb->c = original_c; + mrb->task.scheduler_lock--; + + return result; +} + +/* + * Task.tick class method + */ +static mrb_value +mrb_task_s_tick(mrb_state *mrb, mrb_value self) +{ + return mrb_int_value(mrb, tick_ * MRB_TICK_UNIT); +} + +/* + * Create a task from a proc + * This is called from mrc_create_task() in mrc_utils.c + */ +MRB_API mrb_value +mrb_create_task(mrb_state *mrb, struct RProc *proc, mrb_value name, mrb_value priority, mrb_value top_self) +{ + /* Check scheduler lock */ + if (mrb->task.scheduler_lock > 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot create task during synchronous execution"); + } + + /* Validate/default priority */ + mrb_int prio = 128; /* Default priority */ + if (!mrb_nil_p(priority)) { + if (!mrb_integer_p(priority)) { + mrb_raise(mrb, E_TYPE_ERROR, "priority must be an Integer"); + } + prio = mrb_integer(priority); + if (prio < 0 || prio > 255) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "priority must be 0-255"); + } + } + + /* Validate/default name */ + mrb_value name_val = mrb_nil_p(name) ? mrb_str_new_lit(mrb, "(noname)") : name; + + /* Allocate and initialize task */ + mrb_task *t = task_alloc(mrb); + t->priority = (uint8_t)prio; + t->status = MRB_TASK_STATUS_READY; + t->reason = MRB_TASK_REASON_NONE; + t->name = name_val; + + /* Create Ruby object to hold task */ + mrb_value task_obj = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_get(mrb, "Task"), + t, &mrb_task_type)); + t->self = task_obj; + + /* Register with GC to protect task object from collection */ + mrb_gc_register(mrb, task_obj); + + /* Initialize task context */ + task_init_context(mrb, t, proc); + + /* Set top_self if provided */ + if (!mrb_nil_p(top_self)) { + t->c.ci->stack[0] = top_self; + } + + /* Insert into ready queue */ + mrb_task_disable_irq(); + q_insert_task(mrb, t); + mrb_task_enable_irq(); + + /* Trigger context switch if this task has higher priority than current */ + if (q_ready_ && q_ready_->status == MRB_TASK_STATUS_RUNNING) { + if (t->priority < q_ready_->priority) { + switching_ = TRUE; + } + } + + return task_obj; +} + +/* + * Suspend a task + */ +MRB_API void +mrb_suspend_task(mrb_state *mrb, mrb_value task) +{ + if (mrb->task.scheduler_lock > 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot use asynchronous Task API during synchronous execution"); + } + + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); + if (!t) return; + + if (t->status == MRB_TASK_STATUS_SUSPENDED) return; + + task_change_state(mrb, t, MRB_TASK_STATUS_SUSPENDED); + + /* Always trigger context switch when suspending */ + switching_ = TRUE; +} + +/* + * Resume a task + */ +MRB_API void +mrb_resume_task(mrb_state *mrb, mrb_value task) +{ + if (mrb->task.scheduler_lock > 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot use asynchronous Task API during synchronous execution"); + } + + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); + if (!t) return; + + if (t->status != MRB_TASK_STATUS_SUSPENDED) return; + + /* Determine target state based on reason */ + uint8_t target_status = (t->reason == MRB_TASK_REASON_NONE) ? + MRB_TASK_STATUS_READY : MRB_TASK_STATUS_WAITING; + + task_change_state(mrb, t, target_status); + + /* Trigger context switch if resumed task has higher priority */ + if (target_status == MRB_TASK_STATUS_READY && q_ready_ && + q_ready_->status == MRB_TASK_STATUS_RUNNING) { + if (t->priority < q_ready_->priority) { + switching_ = TRUE; + } + } + + /* Update wakeup_tick if task has sleep reason */ + if (t->reason == MRB_TASK_REASON_SLEEP) { + if ((int32_t)(t->wait.wakeup_tick - wakeup_tick_) < 0) { + wakeup_tick_ = t->wait.wakeup_tick; + } + } +} + +/* + * Terminate a task + */ +MRB_API void +mrb_terminate_task(mrb_state *mrb, mrb_value task) +{ + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); + if (!t) return; + + if (t->status == MRB_TASK_STATUS_DORMANT) return; + + mrb_task_disable_irq(); + q_delete_task(mrb, t); + t->status = MRB_TASK_STATUS_DORMANT; + t->c.status = MRB_TASK_STOPPED; + q_insert_task(mrb, t); + mrb_task_enable_irq(); + + wake_up_join_waiters(mrb, t); +} + +/* + * Stop a task (mark as stopped but don't move to dormant) + */ +MRB_API mrb_bool +mrb_stop_task(mrb_state *mrb, mrb_value task) +{ + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); + if (!t) return FALSE; + + if (t->c.status == MRB_TASK_STOPPED) { + return FALSE; /* Already stopped */ + } + t->c.status = MRB_TASK_STOPPED; + return TRUE; +} + +/* + * Get task result value + */ +MRB_API mrb_value +mrb_task_value(mrb_state *mrb, mrb_value task) +{ + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); + if (!t) return mrb_nil_value(); + + return t->state.result; +} + +/* + * Initialize task context with a new proc + */ +MRB_API void +mrb_task_init_context(mrb_state *mrb, mrb_value task, struct RProc *proc) +{ + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); + if (!t) return; + + struct mrb_context *c = &t->c; + + /* Cleanup existing context if any */ + if (c->stbase) { + mrb_free(mrb, c->stbase); + c->stbase = NULL; + } + if (c->cibase) { + mrb_free(mrb, c->cibase); + c->cibase = NULL; + } + + /* Re-initialize context */ + task_init_context(mrb, t, proc); +} + +/* + * Reset task context to initial state + */ +MRB_API void +mrb_task_reset_context(mrb_state *mrb, mrb_value task) +{ + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); + if (!t) return; + + struct mrb_context *c = &t->c; + c->ci = c->cibase; + c->status = MRB_TASK_CREATED; + if (c->ci) { + mrb_vm_ci_target_class_set(c->ci, mrb->object_class); + } +} + +/* + * Set proc for task + */ +MRB_API void +mrb_task_proc_set(mrb_state *mrb, mrb_value task, struct RProc *proc) +{ + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); + if (!t) return; + + /* Handle environment resize if needed */ + if (t->c.cibase && t->c.cibase->u.env) { + struct REnv *e = mrb_vm_ci_env(t->c.cibase); + if (e && MRB_ENV_LEN(e) < proc->body.irep->nlocals) { + MRB_ENV_SET_LEN(e, proc->body.irep->nlocals); + } + } + + if (t->c.ci) { + mrb_vm_ci_proc_set(t->c.ci, proc); + } +} + /* * Initialization */ @@ -1124,8 +1494,9 @@ mrb_mruby_task_gem_init(mrb_state *mrb) /* Initialize HAL (timer and interrupts) */ mrb_hal_task_init(mrb); - /* Initialize main task to NULL */ + /* Initialize main task to NULL and scheduler_lock to 0 */ mrb->task.main_task = NULL; + mrb->task.scheduler_lock = 0; task_class = mrb_define_class(mrb, "Task", mrb->object_class); MRB_SET_INSTANCE_TT(task_class, MRB_TT_DATA); @@ -1138,6 +1509,7 @@ mrb_mruby_task_gem_init(mrb_state *mrb) mrb_define_class_method_id(mrb, task_class, MRB_SYM(stat), mrb_task_s_stat, MRB_ARGS_NONE()); mrb_define_class_method_id(mrb, task_class, MRB_SYM(get), mrb_task_s_get, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, task_class, MRB_SYM(run), mrb_task_s_run, MRB_ARGS_NONE()); + mrb_define_class_method_id(mrb, task_class, MRB_SYM(tick), mrb_task_s_tick, MRB_ARGS_NONE()); /* Instance methods */ mrb_define_method_id(mrb, task_class, MRB_SYM(status), mrb_task_status, MRB_ARGS_NONE()); diff --git a/src/vm.c b/src/vm.c index b3bb28a8ba..1ffbf910e2 100644 --- a/src/vm.c +++ b/src/vm.c @@ -2691,6 +2691,15 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq return v; } +#ifdef MRB_USE_TASK_SCHEDULER + if (mrb->c->status == MRB_TASK_CREATED) { + mrb_gc_arena_restore(mrb, ai); + mrb->jmp = prev_jmp; + TASK_STOP(mrb); + return v; + } +#endif + fiber_terminate(mrb, c, ci); if (c->vmexec || (mrb->c == mrb->root_c && mrb->c->ci == mrb->c->cibase) /* case using Fiber#transfer in mrb_fiber_resume() */) { From 8a0263026e7cb988be16536efafda0d5438b6e02 Mon Sep 17 00:00:00 2001 From: HASUMI Hitoshi Date: Mon, 12 Jan 2026 12:58:50 +0900 Subject: [PATCH 2/6] Add scheduler_lock check And refactoring to consolidate duplicate code ref PR #6699 --- mrbgems/mruby-task/src/task.c | 86 ++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/mrbgems/mruby-task/src/task.c b/mrbgems/mruby-task/src/task.c index c60c291d8e..711bee15de 100644 --- a/mrbgems/mruby-task/src/task.c +++ b/mrbgems/mruby-task/src/task.c @@ -48,6 +48,18 @@ /* Convert microseconds to tick count */ #define USEC_TO_TICKS(usec) (((usec) / 1000) / MRB_TICK_UNIT) +/* Maximum value for scheduler_lock (uint8_t max) */ +#define MRB_TASK_SCHEDULER_LOCK_MAX 255 + +/* Check scheduler lock and raise error if locked */ +static inline void +task_check_scheduler_lock(mrb_state *mrb) +{ + if (mrb->task.scheduler_lock > 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot use asynchronous Task API during synchronous execution"); + } +} + /* * Task data type for GC */ @@ -208,6 +220,24 @@ q_delete_task(mrb_state *mrb, mrb_task *t) } } +/* Cleanup terminated task and move to dormant queue if needed */ +static inline mrb_bool +task_cleanup_if_stopped(mrb_state *mrb, mrb_task *t) +{ + if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_TASK_STOPPED) { + /* Task is terminated but still in queue - remove it */ + mrb_task_disable_irq(); + q_delete_task(mrb, t); + if (t->status != MRB_TASK_STATUS_DORMANT) { + t->status = MRB_TASK_STATUS_DORMANT; + q_insert_task(mrb, t); + } + mrb_task_enable_irq(); + return TRUE; + } + return FALSE; +} + /* * Task lifecycle */ @@ -439,15 +469,7 @@ mrb_task_run(mrb_state *mrb) } /* Safety check - don't execute terminated tasks */ - if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_TASK_STOPPED) { - /* Task is terminated but still in queue - remove it */ - mrb_task_disable_irq(); - q_delete_task(mrb, t); - if (t->status != MRB_TASK_STATUS_DORMANT) { - t->status = MRB_TASK_STATUS_DORMANT; - q_insert_task(mrb, t); - } - mrb_task_enable_irq(); + if (task_cleanup_if_stopped(mrb, t)) { continue; } @@ -480,15 +502,7 @@ mrb_task_run_once(mrb_state *mrb) } /* Safety check - don't execute terminated tasks */ - if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_TASK_STOPPED) { - /* Task is terminated but still in queue - remove it */ - mrb_task_disable_irq(); - q_delete_task(mrb, t); - if (t->status != MRB_TASK_STATUS_DORMANT) { - t->status = MRB_TASK_STATUS_DORMANT; - q_insert_task(mrb, t); - } - mrb_task_enable_irq(); + if (task_cleanup_if_stopped(mrb, t)) { return mrb_true_value(); } @@ -775,14 +789,7 @@ task_run_one_iteration(mrb_state *mrb) } /* Skip terminated tasks */ - if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_TASK_STOPPED) { - mrb_task_disable_irq(); - q_delete_task(mrb, t); - if (t->status != MRB_TASK_STATUS_DORMANT) { - t->status = MRB_TASK_STATUS_DORMANT; - q_insert_task(mrb, t); - } - mrb_task_enable_irq(); + if (task_cleanup_if_stopped(mrb, t)) { return; } @@ -1166,7 +1173,9 @@ mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc_val, mrb_int argc, struct RProc *proc = mrb_proc_ptr(proc_val); /* 1. Lock scheduler and save context */ - mrb_assert(mrb->task.scheduler_lock < 254); /* Prevent overflow */ + if (mrb->task.scheduler_lock >= MRB_TASK_SCHEDULER_LOCK_MAX) { + mrb_raise(mrb, E_RUNTIME_ERROR, "scheduler lock overflow"); + } mrb->task.scheduler_lock++; struct mrb_context *original_c = mrb->c; @@ -1253,10 +1262,7 @@ mrb_task_s_tick(mrb_state *mrb, mrb_value self) MRB_API mrb_value mrb_create_task(mrb_state *mrb, struct RProc *proc, mrb_value name, mrb_value priority, mrb_value top_self) { - /* Check scheduler lock */ - if (mrb->task.scheduler_lock > 0) { - mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot create task during synchronous execution"); - } + task_check_scheduler_lock(mrb); /* Validate/default priority */ mrb_int prio = 128; /* Default priority */ @@ -1317,9 +1323,7 @@ mrb_create_task(mrb_state *mrb, struct RProc *proc, mrb_value name, mrb_value pr MRB_API void mrb_suspend_task(mrb_state *mrb, mrb_value task) { - if (mrb->task.scheduler_lock > 0) { - mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot use asynchronous Task API during synchronous execution"); - } + task_check_scheduler_lock(mrb); mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); if (!t) return; @@ -1338,9 +1342,7 @@ mrb_suspend_task(mrb_state *mrb, mrb_value task) MRB_API void mrb_resume_task(mrb_state *mrb, mrb_value task) { - if (mrb->task.scheduler_lock > 0) { - mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot use asynchronous Task API during synchronous execution"); - } + task_check_scheduler_lock(mrb); mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); if (!t) return; @@ -1375,6 +1377,8 @@ mrb_resume_task(mrb_state *mrb, mrb_value task) MRB_API void mrb_terminate_task(mrb_state *mrb, mrb_value task) { + task_check_scheduler_lock(mrb); + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); if (!t) return; @@ -1396,6 +1400,8 @@ mrb_terminate_task(mrb_state *mrb, mrb_value task) MRB_API mrb_bool mrb_stop_task(mrb_state *mrb, mrb_value task) { + task_check_scheduler_lock(mrb); + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); if (!t) return FALSE; @@ -1424,6 +1430,8 @@ mrb_task_value(mrb_state *mrb, mrb_value task) MRB_API void mrb_task_init_context(mrb_state *mrb, mrb_value task, struct RProc *proc) { + task_check_scheduler_lock(mrb); + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); if (!t) return; @@ -1449,6 +1457,8 @@ mrb_task_init_context(mrb_state *mrb, mrb_value task, struct RProc *proc) MRB_API void mrb_task_reset_context(mrb_state *mrb, mrb_value task) { + task_check_scheduler_lock(mrb); + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); if (!t) return; @@ -1466,6 +1476,8 @@ mrb_task_reset_context(mrb_state *mrb, mrb_value task) MRB_API void mrb_task_proc_set(mrb_state *mrb, mrb_value task, struct RProc *proc) { + task_check_scheduler_lock(mrb); + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); if (!t) return; From 63dd1832bc7db484e32e59d26528c5cc07d38789 Mon Sep 17 00:00:00 2001 From: HASUMI Hitoshi Date: Wed, 14 Jan 2026 14:12:51 +0900 Subject: [PATCH 3/6] Improve memory management of mrb_execute_proc_synchronously Wrap sync task by mrb_gc_arena_save/restore to release objects from arena that a sync task allocated so that they can be freed in GC cycle --- mrbgems/mruby-task/src/task.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mrbgems/mruby-task/src/task.c b/mrbgems/mruby-task/src/task.c index 711bee15de..81c5aafe2e 100644 --- a/mrbgems/mruby-task/src/task.c +++ b/mrbgems/mruby-task/src/task.c @@ -1171,6 +1171,7 @@ MRB_API mrb_value mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc_val, mrb_int argc, const mrb_value *argv) { struct RProc *proc = mrb_proc_ptr(proc_val); + int ai = mrb_gc_arena_save(mrb); /* 1. Lock scheduler and save context */ if (mrb->task.scheduler_lock >= MRB_TASK_SCHEDULER_LOCK_MAX) { @@ -1228,6 +1229,9 @@ mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc_val, mrb_int argc, q_delete_task(mrb, t); mrb_task_enable_irq(); + /* Prevent double-free: clear Data object's type before freeing task */ + DATA_TYPE(task_obj) = NULL; + /* Free context resources directly (bypass GC since we own this task) */ if (t->c.stbase) { mrb_free(mrb, t->c.stbase); @@ -1243,6 +1247,8 @@ mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc_val, mrb_int argc, mrb->c = original_c; mrb->task.scheduler_lock--; + mrb_gc_arena_restore(mrb, ai); + return result; } From 5c6771eb78eb531a52dda8cd702f3303ebd71578 Mon Sep 17 00:00:00 2001 From: HASUMI Hitoshi Date: Wed, 14 Jan 2026 14:56:50 +0900 Subject: [PATCH 4/6] Remove unnecessary code --- mrbgems/hal-posix-task/src/task_hal.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/mrbgems/hal-posix-task/src/task_hal.c b/mrbgems/hal-posix-task/src/task_hal.c index 87b81adbcd..6e293b704a 100644 --- a/mrbgems/hal-posix-task/src/task_hal.c +++ b/mrbgems/hal-posix-task/src/task_hal.c @@ -115,9 +115,6 @@ mrb_hal_task_init(mrb_state *mrb) /* Unblock SIGALRM */ sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL); -#else - /* WASM: JavaScript handles tick calls via setInterval, no timer needed here */ - (void)i; #endif } From e7d6def80851b8708c9fdea4e22fb146727502e3 Mon Sep 17 00:00:00 2001 From: HASUMI Hitoshi Date: Wed, 14 Jan 2026 15:48:36 +0900 Subject: [PATCH 5/6] Refactor Task#suspend,terminate,resume - Fix inconsistency of MRB_API functions and Ruby methods - Get rid of duplication - Adjust error handling --- mrbgems/mruby-task/src/task.c | 138 +++++++++++++++++----------------- 1 file changed, 67 insertions(+), 71 deletions(-) diff --git a/mrbgems/mruby-task/src/task.c b/mrbgems/mruby-task/src/task.c index 81c5aafe2e..a8a60a8bfd 100644 --- a/mrbgems/mruby-task/src/task.c +++ b/mrbgems/mruby-task/src/task.c @@ -1032,33 +1032,22 @@ mrb_task_set_priority(mrb_state *mrb, mrb_value self) return mrb_fixnum_value(priority); } +/* + * Forward declarations for internal functions + */ +static void suspend_task_internal(mrb_state *mrb, mrb_task *t); +static void resume_task_internal(mrb_state *mrb, mrb_task *t); +static void terminate_task_internal(mrb_state *mrb, mrb_task *t); + static mrb_value mrb_task_suspend(mrb_state *mrb, mrb_value self) { mrb_task *t; TASK_GET_PTR_OR_RAISE(t, self); + task_check_scheduler_lock(mrb); - /* - * WAITING task should also be suspended: - * Suspend trigger possible happens when it is sleeping (WAITING). - * As in, DORMANT task should also be suspended: - * IRB in PicoRuby suspends a DORMANT task to use it again. - */ - - /* - * Determine if context switch is needed BEFORE changing state. - * Context switch is needed when suspending a RUNNING task or - * the current ready task. - */ - mrb_bool need_switch = (t == q_ready_ || t->status == MRB_TASK_STATUS_RUNNING); - - task_change_state(mrb, t, MRB_TASK_STATUS_SUSPENDED); - - if (need_switch) { - switching_ = TRUE; - } - + suspend_task_internal(mrb, t); return self; } @@ -1068,21 +1057,9 @@ mrb_task_resume(mrb_state *mrb, mrb_value self) mrb_task *t; TASK_GET_PTR_OR_RAISE(t, self); + task_check_scheduler_lock(mrb); - /* Can only resume suspended tasks */ - if (t->status != MRB_TASK_STATUS_SUSPENDED) { - return self; - } - - task_change_state(mrb, t, MRB_TASK_STATUS_READY); - - /* Trigger context switch if resumed task has higher priority */ - if (q_ready_ && q_ready_->status == MRB_TASK_STATUS_RUNNING) { - if (t->priority < q_ready_->priority) { - switching_ = TRUE; - } - } - + resume_task_internal(mrb, t); return self; } @@ -1092,30 +1069,9 @@ mrb_task_terminate(mrb_state *mrb, mrb_value self) mrb_task *t; TASK_GET_PTR_OR_RAISE(t, self); + task_check_scheduler_lock(mrb); - /* Don't terminate already dormant tasks */ - if (t->status == MRB_TASK_STATUS_DORMANT) { - return self; - } - - mrb_task_disable_irq(); - - /* Move to dormant queue */ - q_delete_task(mrb, t); - t->status = MRB_TASK_STATUS_DORMANT; - t->c.status = MRB_TASK_STOPPED; - q_insert_task(mrb, t); - - mrb_task_enable_irq(); - - /* Wake up tasks waiting on join */ - wake_up_join_waiters(mrb, t); - - /* If terminating self, trigger context switch */ - if (t == q_ready_) { - switching_ = TRUE; - } - + terminate_task_internal(mrb, t); return self; } @@ -1324,35 +1280,47 @@ mrb_create_task(mrb_state *mrb, struct RProc *proc, mrb_value name, mrb_value pr } /* - * Suspend a task + * Internal: Suspend a task (no validation, no scheduler_lock check) */ -MRB_API void -mrb_suspend_task(mrb_state *mrb, mrb_value task) +static void +suspend_task_internal(mrb_state *mrb, mrb_task *t) { - task_check_scheduler_lock(mrb); - - mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); - if (!t) return; - if (t->status == MRB_TASK_STATUS_SUSPENDED) return; + /* + * Determine if context switch is needed BEFORE changing state. + * Context switch is needed when suspending a RUNNING task or + * the current ready task. + */ + mrb_bool need_switch = (t == q_ready_ || t->status == MRB_TASK_STATUS_RUNNING); + task_change_state(mrb, t, MRB_TASK_STATUS_SUSPENDED); - /* Always trigger context switch when suspending */ - switching_ = TRUE; + if (need_switch) { + switching_ = TRUE; + } } /* - * Resume a task + * Suspend a task */ MRB_API void -mrb_resume_task(mrb_state *mrb, mrb_value task) +mrb_suspend_task(mrb_state *mrb, mrb_value task) { task_check_scheduler_lock(mrb); mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); if (!t) return; + suspend_task_internal(mrb, t); +} + +/* + * Internal: Resume a task (no validation, no scheduler_lock check) + */ +static void +resume_task_internal(mrb_state *mrb, mrb_task *t) +{ if (t->status != MRB_TASK_STATUS_SUSPENDED) return; /* Determine target state based on reason */ @@ -1378,16 +1346,25 @@ mrb_resume_task(mrb_state *mrb, mrb_value task) } /* - * Terminate a task + * Resume a task */ MRB_API void -mrb_terminate_task(mrb_state *mrb, mrb_value task) +mrb_resume_task(mrb_state *mrb, mrb_value task) { task_check_scheduler_lock(mrb); mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); if (!t) return; + resume_task_internal(mrb, t); +} + +/* + * Internal: Terminate a task (no validation, no scheduler_lock check) + */ +static void +terminate_task_internal(mrb_state *mrb, mrb_task *t) +{ if (t->status == MRB_TASK_STATUS_DORMANT) return; mrb_task_disable_irq(); @@ -1398,6 +1375,25 @@ mrb_terminate_task(mrb_state *mrb, mrb_value task) mrb_task_enable_irq(); wake_up_join_waiters(mrb, t); + + /* If terminating self, trigger context switch */ + if (t == q_ready_) { + switching_ = TRUE; + } +} + +/* + * Terminate a task + */ +MRB_API void +mrb_terminate_task(mrb_state *mrb, mrb_value task) +{ + task_check_scheduler_lock(mrb); + + mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); + if (!t) return; + + terminate_task_internal(mrb, t); } /* From 219588091b040d4acdcbbc541f667599e7a17223 Mon Sep 17 00:00:00 2001 From: HASUMI Hitoshi Date: Fri, 16 Jan 2026 08:56:49 +0900 Subject: [PATCH 6/6] Improve task.c code clarity and fix potential GC issue - Add mrb_gc_protect() after arena_restore to prevent result from being collected before returning to caller - Add comment to suspend_task_internal explaining why WAITING and DORMANT tasks can also be suspended - Move argc/argv cast at the beginning of function with comment --- mrbgems/mruby-task/src/task.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/mrbgems/mruby-task/src/task.c b/mrbgems/mruby-task/src/task.c index a8a60a8bfd..0404c2fc70 100644 --- a/mrbgems/mruby-task/src/task.c +++ b/mrbgems/mruby-task/src/task.c @@ -1129,6 +1129,14 @@ mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc_val, mrb_int argc, struct RProc *proc = mrb_proc_ptr(proc_val); int ai = mrb_gc_arena_save(mrb); + /* + * argc/argv are reserved for future use (e.g., passing arguments to + * event handlers or callback functions). Currently all callers pass + * 0 and NULL. + */ + (void)argc; + (void)argv; + /* 1. Lock scheduler and save context */ if (mrb->task.scheduler_lock >= MRB_TASK_SCHEDULER_LOCK_MAX) { mrb_raise(mrb, E_RUNTIME_ERROR, "scheduler lock overflow"); @@ -1151,10 +1159,6 @@ mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc_val, mrb_int argc, mrb_value task_obj = mrb_obj_value(mrb_data_object_alloc(mrb, task_class, t, &mrb_task_type)); t->self = task_obj; - /* Arguments are currently unused in this implementation */ - (void)argc; - (void)argv; - /* 3. Move task from DORMANT to READY */ mrb_task_disable_irq(); t->status = MRB_TASK_STATUS_READY; @@ -1204,6 +1208,7 @@ mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc_val, mrb_int argc, mrb->task.scheduler_lock--; mrb_gc_arena_restore(mrb, ai); + mrb_gc_protect(mrb, result); return result; } @@ -1285,6 +1290,12 @@ mrb_create_task(mrb_state *mrb, struct RProc *proc, mrb_value name, mrb_value pr static void suspend_task_internal(mrb_state *mrb, mrb_task *t) { + /* + * WAITING task should also be suspended: + * Suspend trigger may occur while the task is sleeping (WAITING). + * DORMANT task should also be suspended: + * e.g., IRB in PicoRuby suspends a DORMANT task to use it again. + */ if (t->status == MRB_TASK_STATUS_SUSPENDED) return; /*