mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
Merge pull request #6699 from hasumikin/fix/mruby-task
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 <mruby.h>
|
||||
#include "task_hal.h"
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
@@ -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,23 @@ mrb_hal_task_init(mrb_state *mrb)
|
||||
|
||||
/* Unblock SIGALRM */
|
||||
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
|
||||
#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 +196,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 +227,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
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 */
|
||||
|
||||
+479
-82
@@ -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
|
||||
*/
|
||||
@@ -264,8 +294,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 +352,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 +383,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 +401,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 +449,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,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_FIBER_TERMINATED) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -478,6 +490,38 @@ 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 (task_cleanup_if_stopped(mrb, t)) {
|
||||
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,14 +789,7 @@ task_run_one_iteration(mrb_state *mrb)
|
||||
}
|
||||
|
||||
/* Skip terminated tasks */
|
||||
if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_FIBER_TERMINATED) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -839,7 +876,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 +896,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;
|
||||
@@ -995,25 +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);
|
||||
|
||||
/* Can only suspend ready or running tasks */
|
||||
if (t->status != MRB_TASK_STATUS_READY && t->status != MRB_TASK_STATUS_RUNNING) {
|
||||
return self;
|
||||
}
|
||||
|
||||
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) {
|
||||
switching_ = TRUE;
|
||||
}
|
||||
|
||||
suspend_task_internal(mrb, t);
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -1023,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;
|
||||
}
|
||||
|
||||
@@ -1047,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;
|
||||
}
|
||||
|
||||
@@ -1112,6 +1113,400 @@ 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);
|
||||
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");
|
||||
}
|
||||
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;
|
||||
|
||||
/* 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();
|
||||
|
||||
/* 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);
|
||||
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--;
|
||||
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
mrb_gc_protect(mrb, result);
|
||||
|
||||
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)
|
||||
{
|
||||
task_check_scheduler_lock(mrb);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal: Suspend a task (no validation, no scheduler_lock check)
|
||||
*/
|
||||
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;
|
||||
|
||||
/*
|
||||
* 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 a task
|
||||
*/
|
||||
MRB_API void
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Resume a task
|
||||
*/
|
||||
MRB_API void
|
||||
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();
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
task_check_scheduler_lock(mrb);
|
||||
|
||||
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)
|
||||
{
|
||||
task_check_scheduler_lock(mrb);
|
||||
|
||||
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)
|
||||
{
|
||||
task_check_scheduler_lock(mrb);
|
||||
|
||||
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)
|
||||
{
|
||||
task_check_scheduler_lock(mrb);
|
||||
|
||||
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 +1519,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 +1534,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());
|
||||
|
||||
@@ -2691,6 +2691,15 @@ RETRY_TRY_BLOCK:
|
||||
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() */) {
|
||||
|
||||
Reference in New Issue
Block a user