Module std::task

Concurrent tasks as an effect: code starts tasks only through a Tasks capability in its row, so its caller's executor decides where and when they run. A Tasks capability is also a std::time::Clock, so code written with Clock runs in a task unchanged. Use Pool in a program and std::sim::world (deterministic, one seed one schedule) in a test.

A task waits for another through a key: Tasks::waker makes one, Tasks::park suspends the task under it and Tasks::wake readies it again. A key parks one task, once; a wake that comes before the park is kept, so that park returns at once and a race between the two loses nothing. std::sync's Notify, Watch and Channel are built on them.

Every executor of std is shared over a portable state and may move a task to another thread, so a bare with Tasks means Tasks portable migrates cross, the bound under which a function runs under each of them; a bound written in the row replaces it.

Source: std/task.weir; committed interface: std/task.iface. Index

effect Tasks

pub effect Tasks : Clock default portable migrates cross {
    fn spawn(f: held fn());
    fn stop();
    once fn yield_now();
    once fn preempt();
    fn waker() -> Waker;
    once fn park(w: Waker);
    fn wake(w: Waker);
}

Starting tasks, waiting on keys, and the clock they sleep on: what an executor handles.

op spawn

fn spawn(f: held fn());

Control kind tail: the clause's result is the op's, and the performer goes on, never aborted or suspended.

Starts f as a task of this executor, which decides when, and on which thread, it runs.

op stop

fn stop();

Control kind tail: the clause's result is the op's, and the performer goes on, never aborted or suspended.

Asks the executor to stop: each of its tasks is cancelled at its next suspension, and a task spawned from then on before it starts, as the executor's doc says.

op yield_now

once fn yield_now();

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

Gives up the task's turn: it is ready again at once, behind the tasks ready now, so it is a scheduling point (a choice under weir explore) that waits for nothing; a stopped executor cancels the task there.

op preempt

once fn preempt();

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

A point where another task may run before this one goes on, as it may on any of an executor's threads. An executor whose threads run tasks in parallel does nothing here: they overtake the task anyway. std::sim's worlds, which run every task on one thread, make it a scheduling point (systems.md §21.17): under Search a rare arm that runs every other task ready now first. std::sync's waits begin with it; a wait built on waker and park calls it where it begins to wait, after the check its wait depends on and before it hands its key to the task that will wake it.

op waker

fn waker() -> Waker;

Control kind tail: the clause's result is the op's, and the performer goes on, never aborted or suspended.

A fresh key to park one task under, once, and wake it by.

op park

once fn park(w: Waker);

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

Suspends the task under w until w is woken, at once if it already was; a stopped executor cancels the task there.

op wake

fn wake(w: Waker);

Control kind tail: the clause's result is the op's, and the performer goes on, never aborted or suspended.

Readies the task parked under w, or the park under w still to come.

handler Pool

pub handler Pool(
    s: Slots<Unit, once>,
    workers: usize,
    stopped: Atomic<Bool>,
): shared strict cross for Tasks with Reactor, Sys, th: Threads

The work-stealing executor, for a program's tasks.

workers threads run ready tasks, each keeping its own queue and stealing the oldest task of another when idle; a task costs its frame, not a thread. Install it as with Pool(slots(), 4, atomic(false)) { … } (needs the root's Reactor, Sys and Threads): slots() is its task table, workers must be at least 1 (with none a spawned task never runs), and stopped must start unset. now reads the monotonic clock and sleep parks the task on the reactor's timer, both in nanoseconds. The with ends once the body and every task have ended.

Cancellation: once a task or the with body calls stop(), the pool cancels each task at its next suspension. A task spawned from then on is cancelled before it starts (its move(… on_abort …) entries run), a task that sleeps, yields or parks is cancelled from there (its drop glue and on_abort blocks run), and a sleep in flight is asked to cancel (a timer that has already fired wins, and its task is cancelled at its next sleep). A task already running, or ready, runs to its next suspension. A task parked under a key no task wakes is cancelled once every other task has ended.

Handles Tasks, with clauses: