Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/mruby.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
27 changes: 24 additions & 3 deletions mrbgems/hal-posix-task/src/task_hal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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;
Expand All @@ -40,6 +47,7 @@ sigalrm_handler(int sig)
}
}
}
#endif /* __EMSCRIPTEN__ */

/*
* HAL Interface Implementation
Expand All @@ -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++) {
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
}

/*
Expand Down
147 changes: 147 additions & 0 deletions mrbgems/mruby-task/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
39 changes: 36 additions & 3 deletions mrbgems/mruby-task/include/task.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Loading