Expand description
Schedulers for executing tasks.
In order to execute asynchronous tasks, a system must have one or more schedulers. A scheduler (also sometimes referred to as an executor) is a component responsible for tracking which tasks have been woken, and polling them when they are ready to make progress.
This module contains scheduler implementations for use with the maitake
task system.
§Using Schedulers
This module provides two types which can be used as schedulers. These types differ based on how the core data of the scheduler is shared with tasks spawned on that scheduler:
Scheduler: a reference-counted single-core scheduler (requires the “alloc” feature). AScheduleris internally implemented using anArc, and each task spawned on aSchedulerholds anArcclone of the scheduler core.StaticScheduler: a single-core scheduler stored in astaticvariable. AStaticScheduleris referenced by tasks spawned on it as an&'static StaticSchedulerreference. Therefore, it can be used without requiringalloc, and avoids atomic reference count increments when spawning tasks. However, in order to be used, aStaticSchedulermust be stored in a'static, which can limit its usage in some cases.LocalScheduler: a reference-counted scheduler for!SendFutures (requires the “alloc” feature). This type is identical to theSchedulertype, except that it is capable of spawningFutures that do not implementSend, and is itself notSendorSync(it cannot be shared between CPU cores).LocalStaticScheduler: aStaticSchedulervariant for!SendFutures. This type is identical to theStaticSchedulertype, except that it is capable of spawningFutures that do not implementSend, and is itself notSendorSync(it cannot be shared between CPU cores).
The Schedule trait in this module is used by the Task type to
abstract over both types of scheduler that tasks may be spawned on.
§Spawning Tasks
Once a scheduler has been constructed, tasks may be spawned on it using the
Scheduler::spawn or StaticScheduler::spawn methods. These methods
allocate a new Box to store the spawned task, and
therefore require the “alloc” feature.
Alternatively, if custom task storage is in use, the
scheduler types also provide Scheduler::spawn_allocated and
StaticScheduler::spawn_allocated methods, which allow spawning a task
that has already been stored in a type implementing the task::Storage
trait. This can be used without the “alloc” feature flag, and is primarily
intended for use in systems where tasks are statically allocated, or where
an alternative allocator API (rather than liballoc) is in use.
Finally, to configure the properties of a task prior to spawning it, both
scheduler types provide Scheduler::build_task and
StaticScheduler::build_task methods. These methods return a
task::Builder struct, which can be used to set properties of a task and
then spawn it on that scheduler.
§Executing Tasks
In order to actually execute the tasks spawned on a scheduler, the scheduler must be driven by dequeueing tasks from its run queue and polling them.
Because maitake is a low-level async runtime “construction kit”
rather than a complete runtime implementation, the interface for driving a
scheduler is tick-based. A tick refers to an iteration of a
scheduler’s run loop, in which a set of tasks are dequeued from the
scheduler’s run queue and polled. Calling the Scheduler::tick or
StaticScheduler::tick method on a scheduler runs that scheduler for a
single tick, returning a Tick struct with data describing the events
that occurred during that tick.
The scheduler API is tick-based, rather than providing methods that continuously tick the scheduler until all tasks have completed, because ticking a scheduler is often only one step of a system’s run loop. A scheduler is responsible for polling the tasks that have been woken, but it does not wake tasks which are waiting for other runtime services, such as timers and I/O resources.
Typically, an iteration of a system’s run loop consists of the following steps:
- Tick the scheduler, executing any tasks that have been woken,
- Tick a timer1, to advance the system clock and wake any tasks waiting for time-based events,
- Process wakeups from I/O resources, such as hardware interrupts that occurred during the tick. The component responsible for this is often referred to as an I/O reactor.
- Optionally, spawn tasks from external sources, such as work-stealing tasks from other schedulers, or receiving tasks from a remote system.
The implementation of the timer and I/O runtime services in a bare-metal
system typically depend on details of the hardware platform in use.
Therefore, maitake does not provide a batteries-included runtime that
bundles together a scheduler, timer, and I/O reactor. Instead, the
lower-level tick-based scheduler interface allows running a maitake
scheduler as part of a run loop implementation that also drives other parts
of the runtime.
A single call to Scheduler::tick will dequeue and poll up to
Scheduler::DEFAULT_TICK_SIZE tasks from the run queue, rather than
looping until all tasks in the queue have been dequeued.
§Examples
A simple implementation of a system’s run loop might look like this:
use maitake::scheduler::Scheduler;
/// Process any time-based events that have occurred since this function
/// was last called.
fn process_timeouts() {
// this might tick a `maitake::time::Timer` or run some other form of
// time driver implementation.
}
/// Process any I/O events that have occurred since this function
/// was last called.
fn process_io_events() {
// this function would handle dispatching any I/O interrupts that
// occurred during the tick to tasks that are waiting for those I/O
// events.
}
/// Put the system into a low-power state until a hardware interrupt
/// occurs.
fn wait_for_interrupts() {
// the implementation of this function would, of course, depend on the
// hardware platform in use...
}
/// The system's main run loop.
fn run_loop() {
let scheduler = Scheduler::new();
loop {
// process time-based events
process_timeouts();
// process I/O events
process_io_events();
// tick the scheduler, running any tasks woken by processing time
// and I/O events, as well as tasks woken by other tasks during the
// tick.
let tick = scheduler.tick();
if !tick.has_remaining {
// if the scheduler's run queue is empty, wait for an interrupt
// to occur before ticking the scheduler again.
wait_for_interrupts();
}
}
}§Scheduling in Multi-Core Systems
WIP ELIZA WRITE THIS
The
maitake::timemodule provides oneTimerimplementation, but other timers could be used as well. ↩
Macros§
- new_
static - Safely constructs a new
StaticSchedulerinstance in astaticinitializer.
Structs§
- Injector
- An injector queue for spawning tasks on multiple
Schedulerinstances. - Local
Scheduler - A reference-counted scheduler for
!Sendtasks. - Local
Spawner - A handle to a
LocalSchedulerthat implementsSend. - Local
Static Scheduler - A statically-initialized scheduler for
!Sendtasks. - Local
Static Spawner - A handle to a
LocalStaticSchedulerthat implementsSend. - Scheduler
- An atomically reference-counted single-core scheduler implementation.
- Static
Scheduler - A statically-initialized scheduler implementation.
- Stealer
- A handle for stealing tasks from a
Scheduler’s run queue, or anInjectorqueue. - Task
Stub - A stub
Task. - Tick
- Metrics recorded during a scheduler tick.
Enums§
- TrySteal
Error - Errors returned by
Injector::try_steal,Scheduler::try_steal, andStaticScheduler::try_steal.
Traits§
- Schedule
- Trait implemented by schedulers.