mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-task: add README.md
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,475 @@
|
||||
# mruby-task
|
||||
|
||||
mruby-task is an mrbgem that provides cooperative multitasking with preemptive
|
||||
scheduling for mruby. It enables concurrent execution of multiple tasks within
|
||||
a single mruby VM instance using a priority-based scheduler with tick-based
|
||||
time slicing.
|
||||
|
||||
## Purpose
|
||||
|
||||
The primary purpose of `mruby-task` is to enable mruby applications to:
|
||||
|
||||
- Execute multiple tasks concurrently within a single VM.
|
||||
- Schedule tasks based on priority (0-255, where 0 is highest priority).
|
||||
- Provide cooperative yielding with `Task.pass`.
|
||||
- Support preemptive scheduling via timer-based interrupts.
|
||||
- Synchronize tasks using `sleep` and `join` operations.
|
||||
- Suspend and resume tasks programmatically.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Task Scheduler
|
||||
|
||||
The scheduler uses four priority-sorted queues:
|
||||
|
||||
- **DORMANT**: Tasks that have not started or have finished execution.
|
||||
- **READY**: Tasks ready to run, ordered by priority.
|
||||
- **WAITING**: Tasks waiting for sleep timeout or join completion.
|
||||
- **SUSPENDED**: Tasks manually suspended via `#suspend`.
|
||||
|
||||
### Tick-Based Preemption
|
||||
|
||||
A platform-specific timer generates periodic ticks (default: 4ms). Each task
|
||||
receives a timeslice (default: 3 ticks = 12ms) before being preempted. The
|
||||
scheduler automatically switches to the next ready task when:
|
||||
|
||||
- A task's timeslice expires.
|
||||
- A task calls `sleep`, `Task.pass`, or `join`.
|
||||
- A task finishes execution.
|
||||
|
||||
## Functionality
|
||||
|
||||
### Creating Tasks
|
||||
|
||||
Tasks are created with `Task.new` and begin execution immediately:
|
||||
|
||||
```ruby
|
||||
# Create a task with default priority (128)
|
||||
task = Task.new do
|
||||
puts "Hello from task!"
|
||||
sleep 1
|
||||
puts "Task resumed"
|
||||
end
|
||||
|
||||
# Create a named task with custom priority
|
||||
task = Task.new(name: "worker", priority: 64) do
|
||||
loop do
|
||||
process_data
|
||||
Task.pass # Yield to other tasks
|
||||
end
|
||||
end
|
||||
|
||||
# Start the scheduler (blocks until all tasks complete or idle)
|
||||
Task.run
|
||||
```
|
||||
|
||||
### Task Class Methods
|
||||
|
||||
- **`Task.new(name: nil, priority: 128) { block }`**: Creates and starts a new task. Lower priority values run first (0 is highest priority).
|
||||
|
||||
```ruby
|
||||
task = Task.new(name: "background", priority: 200) do
|
||||
# Task code here
|
||||
end
|
||||
```
|
||||
|
||||
- **`Task.current`**: Returns the currently executing task.
|
||||
|
||||
```ruby
|
||||
current = Task.current
|
||||
puts "Running: #{current.name}"
|
||||
```
|
||||
|
||||
- **`Task.list`**: Returns an array of all tasks (including dormant tasks).
|
||||
|
||||
```ruby
|
||||
Task.list.each do |task|
|
||||
puts "#{task.name}: #{task.status}"
|
||||
end
|
||||
```
|
||||
|
||||
- **`Task.pass`**: Cooperatively yields execution to other ready tasks.
|
||||
|
||||
```ruby
|
||||
loop do
|
||||
do_work
|
||||
Task.pass # Let other tasks run
|
||||
end
|
||||
```
|
||||
|
||||
- **`Task.get(name)`**: Finds a task by name. Returns `nil` if not found.
|
||||
|
||||
```ruby
|
||||
worker = Task.get("worker")
|
||||
worker.suspend if worker
|
||||
```
|
||||
|
||||
- **`Task.stat`**: Returns scheduler statistics (tick count, wakeup time, task list).
|
||||
|
||||
```ruby
|
||||
stats = Task.stat
|
||||
puts "Tick: #{stats.tick}"
|
||||
```
|
||||
|
||||
- **`Task.run`**: Starts the scheduler main loop. Blocks until no tasks remain ready or waiting.
|
||||
|
||||
```ruby
|
||||
Task.new { do_async_work }
|
||||
Task.run # Run scheduler until tasks complete
|
||||
```
|
||||
|
||||
### Task Instance Methods
|
||||
|
||||
- **`#status`**: Returns the task status as a symbol (`:DORMANT`, `:READY`, `:RUNNING`, `:WAITING`, `:SUSPENDED`).
|
||||
|
||||
```ruby
|
||||
puts task.status # => :READY
|
||||
```
|
||||
|
||||
- **`#name`** / **`#name=`**: Get or set the task name.
|
||||
|
||||
```ruby
|
||||
task.name = "worker-1"
|
||||
puts task.name # => "worker-1"
|
||||
```
|
||||
|
||||
- **`#priority`** / **`#priority=`**: Get or set the task priority (0-255). Changing priority requeues the task.
|
||||
|
||||
```ruby
|
||||
task.priority = 100 # Lower priority
|
||||
```
|
||||
|
||||
- **`#suspend`**: Suspends the task, moving it to the SUSPENDED queue. The task will not run until `#resume` is called.
|
||||
|
||||
```ruby
|
||||
task.suspend
|
||||
# Later...
|
||||
task.resume
|
||||
```
|
||||
|
||||
- **`#resume`**: Resumes a suspended task, moving it to the READY queue.
|
||||
|
||||
```ruby
|
||||
task.resume
|
||||
```
|
||||
|
||||
- **`#terminate`**: Terminates the task immediately, moving it to DORMANT state.
|
||||
|
||||
```ruby
|
||||
task.terminate
|
||||
```
|
||||
|
||||
- **`#join`**: Blocks the current task until the target task completes.
|
||||
|
||||
```ruby
|
||||
worker = Task.new { do_long_operation }
|
||||
worker.join # Wait for completion
|
||||
puts "Worker finished"
|
||||
```
|
||||
|
||||
### Kernel Methods (Sleep)
|
||||
|
||||
The task scheduler provides task-aware sleep methods that cooperatively yield
|
||||
to other tasks:
|
||||
|
||||
- **`sleep(seconds)`**: Sleeps for the specified duration. Accepts integers or floats (when `MRB_NO_FLOAT` is not defined).
|
||||
|
||||
```ruby
|
||||
sleep 1 # Sleep for 1 second
|
||||
sleep 0.5 # Sleep for 500ms (with float support)
|
||||
sleep # Sleep indefinitely (no arguments)
|
||||
```
|
||||
|
||||
- **`usleep(microseconds)`**: Sleeps for the specified number of microseconds.
|
||||
|
||||
```ruby
|
||||
usleep 500000 # Sleep for 500ms
|
||||
usleep 1000 # Sleep for 1ms
|
||||
```
|
||||
|
||||
- **`sleep_ms(milliseconds)`**: Sleeps for the specified number of milliseconds.
|
||||
|
||||
```ruby
|
||||
sleep_ms 100 # Sleep for 100ms
|
||||
```
|
||||
|
||||
**Note**: These methods override `mruby-sleep` when both gems are present. They
|
||||
provide task-aware cooperative sleep when called from within a task, or
|
||||
blocking sleep when called outside task context.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Build Configuration
|
||||
|
||||
Enable the task scheduler by including the gem in your build config:
|
||||
|
||||
```ruby
|
||||
MRuby::Build.new do |conf|
|
||||
# ... other configuration ...
|
||||
|
||||
conf.gem :core => 'mruby-task'
|
||||
|
||||
# ... other gems ...
|
||||
end
|
||||
```
|
||||
|
||||
This automatically defines `MRB_USE_TASK_SCHEDULER`.
|
||||
|
||||
### Timing Configuration
|
||||
|
||||
Timing parameters can be configured via C defines:
|
||||
|
||||
```c
|
||||
#define MRB_TICK_UNIT 4 // Tick period in milliseconds (default: 4ms)
|
||||
#define MRB_TIMESLICE_TICK_COUNT 3 // Ticks per timeslice (default: 3)
|
||||
```
|
||||
|
||||
Default timeslice: `MRB_TICK_UNIT * MRB_TIMESLICE_TICK_COUNT = 12ms`
|
||||
|
||||
### Stack Configuration
|
||||
|
||||
Task stack and call info sizes:
|
||||
|
||||
```c
|
||||
#define TASK_STACK_INIT_SIZE 64 // Initial stack entries (default: 64)
|
||||
#define TASK_CI_INIT_SIZE 8 // Initial callinfo entries (default: 8)
|
||||
```
|
||||
|
||||
These grow automatically as needed, similar to Fiber.
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
### HAL (Hardware Abstraction Layer)
|
||||
|
||||
The task scheduler requires platform-specific timer and interrupt support via four HAL functions:
|
||||
|
||||
```c
|
||||
void mrb_task_hal_init(mrb_state *mrb); // Initialize timer
|
||||
void mrb_task_enable_irq(void); // Enable timer interrupts
|
||||
void mrb_task_disable_irq(void); // Disable timer interrupts
|
||||
void mrb_task_hal_idle_cpu(mrb_state *mrb); // Idle when no tasks ready
|
||||
```
|
||||
|
||||
### POSIX Platform
|
||||
|
||||
On POSIX systems (Linux, macOS, BSD), the implementation uses:
|
||||
|
||||
- `SIGALRM` signal for timer interrupts
|
||||
- `setitimer()` for periodic tick generation
|
||||
- `sigprocmask()` for interrupt enable/disable
|
||||
- `SA_RESTART` flag to prevent `EINTR` on system calls
|
||||
|
||||
### Other Platforms
|
||||
|
||||
For embedded or non-POSIX platforms, you must implement the HAL functions. See
|
||||
the POSIX implementation in `src/task.c` as a reference.
|
||||
|
||||
Example for a hypothetical embedded platform:
|
||||
|
||||
```c
|
||||
void mrb_task_hal_init(mrb_state *mrb) {
|
||||
// Setup hardware timer to call mrb_tick() every MRB_TICK_UNIT ms
|
||||
hardware_timer_init(MRB_TICK_UNIT, timer_irq_handler);
|
||||
}
|
||||
|
||||
void timer_irq_handler(void) {
|
||||
mrb_tick(global_mrb); // Must be called from timer interrupt
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Multitasking
|
||||
|
||||
```ruby
|
||||
Task.new(name: "task1") do
|
||||
3.times do |i|
|
||||
puts "Task 1: #{i}"
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
|
||||
Task.new(name: "task2") do
|
||||
3.times do |i|
|
||||
puts "Task 2: #{i}"
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
|
||||
Task.run # Run until both tasks complete
|
||||
```
|
||||
|
||||
### Priority Scheduling
|
||||
|
||||
```ruby
|
||||
# High priority task (runs first)
|
||||
Task.new(priority: 0) do
|
||||
puts "High priority"
|
||||
sleep 0.1
|
||||
end
|
||||
|
||||
# Low priority task (runs after high priority yields)
|
||||
Task.new(priority: 255) do
|
||||
puts "Low priority"
|
||||
end
|
||||
|
||||
Task.run
|
||||
```
|
||||
|
||||
### Cooperative Yielding
|
||||
|
||||
```ruby
|
||||
Task.new(name: "cooperative") do
|
||||
loop do
|
||||
do_some_work
|
||||
Task.pass # Yield to other tasks
|
||||
break if done?
|
||||
end
|
||||
end
|
||||
|
||||
Task.new(name: "other") do
|
||||
do_other_work
|
||||
end
|
||||
|
||||
Task.run
|
||||
```
|
||||
|
||||
### Task Synchronization
|
||||
|
||||
```ruby
|
||||
worker = Task.new(name: "worker") do
|
||||
puts "Working..."
|
||||
sleep 1
|
||||
puts "Work done"
|
||||
42 # Return value
|
||||
end
|
||||
|
||||
Task.new(name: "main") do
|
||||
puts "Waiting for worker..."
|
||||
worker.join
|
||||
puts "Worker completed!"
|
||||
end
|
||||
|
||||
Task.run
|
||||
```
|
||||
|
||||
### Task Control
|
||||
|
||||
```ruby
|
||||
task = Task.new do
|
||||
loop do
|
||||
puts "Running..."
|
||||
sleep 0.5
|
||||
end
|
||||
end
|
||||
|
||||
# From another task or after Task.run returns:
|
||||
task.suspend # Pause execution
|
||||
sleep 1
|
||||
task.resume # Resume execution
|
||||
sleep 1
|
||||
task.terminate # Stop permanently
|
||||
```
|
||||
|
||||
## Limitations and Compatibility
|
||||
|
||||
### Relationship with Fiber
|
||||
|
||||
Tasks and Fibers both use `mrb_context` but are **not compatible**:
|
||||
|
||||
- Tasks are scheduled automatically by the preemptive scheduler.
|
||||
- Fibers require explicit `Fiber.yield` and `resume` calls.
|
||||
- Do not mix Tasks and Fibers in the same application.
|
||||
|
||||
### Relationship with mruby-sleep
|
||||
|
||||
When `mruby-task` is enabled:
|
||||
|
||||
- The `sleep`, `usleep`, and `sleep_ms` methods are task-aware.
|
||||
- Inside a task, they cooperatively yield to the scheduler.
|
||||
- Outside a task (or when scheduler is idle), they block.
|
||||
- `mruby-sleep` should be excluded from your build when using `mruby-task`.
|
||||
|
||||
### Thread Safety
|
||||
|
||||
The task scheduler is **not thread-safe**. All tasks run in a single OS thread.
|
||||
For multi-core concurrency, use OS threads with separate mruby VMs per thread.
|
||||
|
||||
### Exceptions
|
||||
|
||||
Uncaught exceptions in a task will terminate that task but not affect other
|
||||
tasks. The exception is not propagated to the scheduler.
|
||||
|
||||
### GC Integration
|
||||
|
||||
Task contexts are registered with the garbage collector. Tasks and their
|
||||
stacks/callinfo are properly marked and freed.
|
||||
|
||||
## Testing
|
||||
|
||||
The gem includes tests that verify:
|
||||
|
||||
- Task creation and execution
|
||||
- Priority scheduling
|
||||
- Sleep and wakeup
|
||||
- Join synchronization
|
||||
- Suspend and resume
|
||||
- Task.pass cooperative yielding
|
||||
|
||||
Run tests with:
|
||||
|
||||
```bash
|
||||
rake CONFIG=host-debug test:lib
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Task States
|
||||
|
||||
Each task can be in one of five states:
|
||||
|
||||
- `DORMANT (0x00)`: Not started or finished
|
||||
- `READY (0x02)`: Ready to run
|
||||
- `RUNNING (0x03)`: Currently executing
|
||||
- `WAITING (0x04)`: Waiting (sleep, join, mutex)
|
||||
- `SUSPENDED (0x08)`: Manually suspended
|
||||
|
||||
### Wait Reasons
|
||||
|
||||
When a task is in WAITING state, the reason indicates why:
|
||||
|
||||
- `NONE (0x00)`: No specific reason
|
||||
- `SLEEP (0x01)`: Sleeping for time
|
||||
- `MUTEX (0x02)`: Waiting for mutex (reserved, not yet implemented)
|
||||
- `JOIN (0x04)`: Waiting for another task
|
||||
|
||||
### Scheduler Algorithm
|
||||
|
||||
1. Get the highest-priority task from the READY queue
|
||||
2. Set task status to RUNNING and switch context (`mrb->c = &task->c`)
|
||||
3. Execute task via `mrb_vm_exec()` until:
|
||||
- Task yields (sleep, pass, join)
|
||||
- Timeslice expires (preemption)
|
||||
- Task completes or terminates
|
||||
4. Handle completion (wake joined tasks, move to DORMANT)
|
||||
5. Run incremental GC if needed
|
||||
6. Requeue task to READY (if still running) or appropriate queue
|
||||
7. Repeat from step 1
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Planned features not yet implemented:
|
||||
|
||||
- **Mutex support**: Thread-safe synchronization primitives
|
||||
- **Task.raise**: Throw exceptions to other tasks
|
||||
- **Task#value**: Retrieve task return value (like Thread#value)
|
||||
- **Per-task timeslice configuration**
|
||||
|
||||
## License
|
||||
|
||||
MIT License (same as mruby)
|
||||
|
||||
## See Also
|
||||
|
||||
- `mruby-fiber`: Cooperative fibers with manual control
|
||||
- `mruby-sleep`: Blocking sleep (superseded by mruby-task)
|
||||
Reference in New Issue
Block a user