Skip to content
CryoCryo home
Stdlibfuture

executor

import std::future::executor; · source

Types

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));

new sizes the worker pool to the hardware thread count; with_threads picks the count yourself and with_threads_and_blocking also caps the blocking pool — the threads that run getaddrinfo, file I/O, and child waits off the executor. spawn returns a JoinHandle and the workers begin driving the future immediately; spawn_detached is fire and forget; block_on spawns the root and blocks the calling thread until it finishes.

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

Properties

PropertyType
innerExecInner*
worker_tids[u64]

Constructors

static new() -> Executor;

Create an executor whose pool is sized to the hardware parallelism.

static with_threads(n: u32) -> Executor;

Create an executor with exactly n worker threads (clamped to >= 1 so the pool always makes progress) and the default blocking-pool ceiling.

static with_threads_and_blocking(n: u32, blocking: u32) -> Executor;

Create an executor with n worker threads and a blocking pool that will grow to at most blocking threads.

Methods

spawn<F, O>(mut &this, fut: F) -> JoinHandle<O> where F: Future<O>;

Spawn fut and return a handle to its Output. Workers begin driving it immediately.

spawn_detached<F, O>(mut &this, fut: F) -> void where F: Future<O>;

Spawn fut as fire-and-forget: it runs to completion and its result is discarded. Sugar for spawn(fut).detach().

block_on<F, O>(mut &this, root: F) -> O where F: Future<O>;

Spawn root and block the calling thread until a worker drives it to completion, then return its value. Panics if root was cancelled or could not complete; use spawn(root).join() for the Result.

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).

Properties

PropertyType
sharedTaskShared<O>*
execExecInner*

Methods

join(mut this) -> Result<O, JoinError>;

Block on the completion condvar until a worker finishes this task, then return its Output (or Err if it was aborted / could not complete / panicked). Consumes the handle and frees the block. Waiting on the handshake state (not merely outcome) guarantees the finishing worker is past its last touch of the block before we read+free it.

detach(mut this) -> void;

Give up the ability to join. The task runs to completion independently; its result is dropped. Consumes the handle.

abort(&this) -> void;

Request that the task be cancelled. It is reclaimed — its Output never produced — the next time a worker reaches it (a subsequent join then returns Err(Cancelled)). Does not consume the handle.

is_finished(&this) -> boolean;

True once the task has finished (completed, cancelled, or panicked). Never blocks and does not consume the handle.

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.

Variants
Variant
Cancelled
Panicked(PanicInfo)

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

Executor

implement trait Drop for struct Executor

JoinHandle

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

Constants

const OUTCOME_PENDING:   u8 = 0;
const OUTCOME_READY:     u8 = 1;
const OUTCOME_CANCELLED: u8 = 2;
const OUTCOME_PANICKED:  u8 = 3;
const HS_LIVE:           u8 = 0;
const HS_TASK_FIN:       u8 = 1;
const HS_HANDLE_GONE:    u8 = 2;
const ST_IDLE:           u8 = 0;
const ST_SCHEDULED:      u8 = 1;
const ST_RUNNING:        u8 = 2;
const ST_NOTIFIED:       u8 = 3;