Skip to content
CryoCryo home
Stdlibfuture

executor

import std::future::executor; · source

Executor

type struct Executor {
    inner:       ExecInner*;
    worker_tids: u64[];

    static new() -> Executor;
    static with_threads(n: u32) -> Executor;
    static with_threads_and_blocking(n: u32, blocking: u32) -> Executor;
    spawn<F, O>(mut &this, fut: F) -> JoinHandle<O>
    where F: Future<O>;
    spawn_detached<F, O>(mut &this, fut: F) -> void
    where F: Future<O>;
    block_on<F, O>(mut &this, root: F) -> O
    where F: Future<O>;
}

An Executor owns a queue of ready tasks and a pool of worker threads that drive them, plus the reactor and blocking pool those tasks park on.

import std::future;

mut ex: Executor = Executor::new();
const result: Report = ex.block_on(run_pipeline(config));
MethodNotes
new()Sizes the worker pool to the hardware thread count.
with_threads(n)Pick the worker count yourself.
with_threads_and_blocking(n, blocking)Also cap the blocking pool — the threads that run getaddrinfo, file I/O, and child waits off the executor.
spawn(fut)JoinHandle<O>; workers begin driving it immediately.
spawn_detached(fut)Fire and forget.
block_on(root)Spawns root and blocks the calling thread until it finishes.

join and block_on never poll on the calling thread — the workers own all polling.

JoinHandle<O>

type struct JoinHandle<O> {
    shared: TaskShared<O>*;
    exec:   ExecInner*;

    join(mut this) -> Result<O, JoinError>;
    detach(mut this) -> void;
    abort(&this) -> void;
    is_finished(&this) -> boolean;
}

Dropping a handle without joining or detaching detaches it: the task runs on and its result is discarded. abort() requests cancellation, and the task is reclaimed — its output never produced — the next time a worker reaches it; a join on an aborted task returns Err(Cancelled).

JoinError

type enum JoinError {
    Cancelled;
    Panicked(PanicInfo);
}

Cancelled is what join returns on an aborted task; Panicked carries the panic's message and location, and only occurs under --panic=unwind.

Dropping the Executor signals shutdown, joins the workers, then reclaims any still-queued tasks.

Concurrent-poll safety

With two or more workers a task can be woken — by itself mid-poll, or later by another thread — while a worker is already polling it. Polling one future from two workers at once is a data race, so each task carries an atomic run state (IDLE, SCHEDULED, RUNNING, NOTIFIED) that guarantees a task is enqueued at most once and polled by at most one worker.

wake() moves IDLE → SCHEDULED and enqueues, or RUNNING → NOTIFIED and defers to the running worker. A worker moves SCHEDULED → RUNNING to poll, then RUNNING → IDLE on a quiet Pending, or re-schedules on NOTIFIED.

Task isolation

Under --panic=unwind the poll boundary runs inside a catch: a panicking task is caught, finishes as panicked, and its join returns Err(Panicked(info)) — its siblings keep running.

Under the default --panic=abort the catch machinery is a compile error, so the boundary is a plain direct call and a task panic aborts the whole process. That is the accepted degradation; see catch_unwind.

Trait implementations

implement trait Drop for struct Executor

implement<O> trait Drop for struct JoinHandle<O>