blocking
import std::future::blocking; · source
An off-executor home for work that cannot be polled. A Reactor lets a future sleep on something the OS can report readiness for; some work has no such report. getaddrinfo is a synchronous call with no non-blocking form on either platform, and a regular file is always "ready" as far as epoll is concerned, so neither can be driven by the readiness reactor. Running one directly inside a poll stalls an executor worker for the whole call — on a single-threaded Executor, the whole runtime.
The pool is the standard answer: a small set of OS threads that run such calls off the executor and fire a Waker when they finish. The call still blocks a thread; it blocks one that nobody is polling on.
Types
BlockingPool
type struct BlockingPool {
mtx: [u8;
40];
cv: [u8;
48];
head: BlockingJob*;
tail: BlockingJob*;
shutdown: boolean;
live: u32;
idle: u32;
max: u32;
tids: u64*;
static start() -> BlockingPool*;
static with_max(max: u32) -> BlockingPool*;
static stop(p: BlockingPool*) -> void;
static free(p: BlockingPool*) -> void;
static current() -> BlockingPool*;
static set_current(p: BlockingPool*) -> void;
submit(mut &this, job: BlockingJob*) -> boolean;
take(mut &this) -> BlockingJob*;
}
An Executor starts one and its workers set it as the thread's ambient pool through set_current; current() is how a future finds it. submit queues a job and returns whether a thread will run it.
Properties
| Property | Type |
|---|---|
mtx | [u8 |
40] | |
cv | [u8 |
48] | |
head | BlockingJob* |
tail | BlockingJob* |
shutdown | boolean |
live | u32 |
idle | u32 |
max | u32 |
tids | u64* |
Constructors
static start() -> BlockingPool*;
static start() -> BlockingPool*;Create an empty pool with the default thread ceiling.
static with_max(max: u32) -> BlockingPool*;
static with_max(max: u32) -> BlockingPool*;Create an empty pool that will grow to at most max threads (clamped to >= 1: a pool that could never create a thread would accept jobs and never reach them). No thread is started here — the first submit creates one, so a program that never offloads anything never pays for a thread, and max costs only its slot in the id table. Panics only if the OS refuses the lock primitives or the allocator refuses that table, either of which would make every later submit unsound.
static current() -> BlockingPool*;
static current() -> BlockingPool*;The pool serving the current thread's polls, or null if this thread is not an executor worker.
Static functions
static stop(p: BlockingPool*) -> void;
static stop(p: BlockingPool*) -> void;Stop the pool and release everything it owns, leaving the block itself allocated (as Reactor::stop does, and for the same reason: a future dropped later still lets go of a job through this pool).
static free(p: BlockingPool*) -> void;
static free(p: BlockingPool*) -> void;Release the pool block. Must run only after every future that could still let go of a job has been dropped — see stop.
static set_current(p: BlockingPool*) -> void;
static set_current(p: BlockingPool*) -> void;Publish p as this thread's pool. Called by an executor worker as it starts, so every future it polls can reach the pool.
Methods
submit(mut &this, job: BlockingJob*) -> boolean;
submit(mut &this, job: BlockingJob*) -> boolean;Queue job and make sure a thread will reach it, acquiring the pool's reference to the block. Returns false when the job was NOT accepted — the pool is shutting down, or it has no thread and the OS refused to create one — in which case the caller still owns the block and must run the work itself rather than wait for a completion that will never come.
take(mut &this) -> BlockingJob*;
take(mut &this) -> BlockingJob*;Block until a job is queued or shutdown is signaled. Returns the dequeued job, or null once shutdown is set — leaving anything still queued for stop to cancel.
BlockingJob
type struct BlockingJob {
rc: Atomic<u32>;
status: Atomic<u8>;
run_fn: (u8*) -> void;
drop_fn: (u8*) -> void;
waker: Waker;
next: BlockingJob*;
pool: BlockingPool*;
static init(job: BlockingJob*, run_fn: (u8*) -> void, drop_fn: (u8*) -> void) -> void;
arm(mut &this, w: Waker) -> u8;
finish(mut &this, status: u8) -> void;
retain(mut &this) -> void;
release(mut &this) -> void;
}
Properties
| Property | Type |
|---|---|
rc | Atomic<u32> |
status | Atomic<u8> |
waker | Waker |
next | BlockingJob* |
pool | BlockingPool* |
Static functions
static init(job: BlockingJob*, run_fn: (u8*) -> void, drop_fn: (u8*) -> void) -> void;
static init(job: BlockingJob*, run_fn: (u8*) -> void, drop_fn: (u8*) -> void) -> void;Prepare a freshly allocated block. The refcount starts at 1 — the submitter's own reference — and submit acquires the pool's second one only if it accepts the job, so a refused job needs no unwinding.
Methods
run_fn: (u8*) -> void;
drop_fn: (u8*) -> void;
arm(mut &this, w: Waker) -> u8;
arm(mut &this, w: Waker) -> u8;Read this job's status and install w as the waker to fire when it finishes. Returns the status as of the moment w was stored.
finish(mut &this, status: u8) -> void;
finish(mut &this, status: u8) -> void;Publish status and fire the waker the submitter armed, then release the pool's reference. Called by a pool thread when run_fn returns, and by BlockingPool::stop (with JOB_CANCELLED) for a job no thread reached.
retain(mut &this) -> void;
retain(mut &this) -> void;Acquire one reference to this block, so it cannot be freed while the acquirer still names it. init hands out the first; submit takes the pool's. Pairs one-for-one with release.
release(mut &this) -> void;
release(mut &this) -> void;Drop one reference to this block. The last one releases any waker still stored and hands the block to its payload's drop_fn, which frees it.
The shape of a job
The pool's queue is homogeneous, so a job is type-erased the same way a task and a waker are — a manual vtable of non-capturing function pointers. A user of the pool declares a struct whose first field is a BlockingJob and hands that field's address to submit; run_fn casts the address back to the concrete block and finds its payload beside the header:
type struct ResolveJob {
job: BlockingJob; // first: `submit` is handed &this.job
host: String; // the request, owned by the block
out: Result<..>; // written by `run_fn`, read once it finishes
}
init fills the header with the run and drop functions; the future's poll calls arm(waker) to say who to wake and reads the status it returns; finish is what the pool thread calls when run_fn returns. retain and release are the reference count that lets the future and the pool thread disagree about who finishes last — a cancelled future releases its side and the pool frees the block when the call eventually returns.
Growth
Threads are created on demand, never in advance, and only when no thread is already idle: a pool that grew on every submit would reach its ceiling under a burst one thread could have absorbed. They are not reaped while the pool lives — a blocking pool's threads are asleep in a syscall, not consuming CPU, and an executor's lifetime is the natural bound. Past max threads, jobs queue. Executor::with_threads_and_blocking sets the cap.
net::dns::Resolve, process::ChildWait, and the pipe drain behind Command::collect are the in-tree users.
Constants
const JOB_PENDING: u8 = 0;
const JOB_DONE: u8 = 1;
const JOB_CANCELLED: u8 = 2;
const BLOCKING_DEFAULT_THREADS: u32 = 512;type struct Name { ... }
type enum Name { ... }
type trait Name { ... }
type union Name { ... }Begins a type declaration; the keyword that follows says which kind. On its own, type Name = Existing; declares an alias.
type struct Name {
field: Type;
method(&this) -> Ret { ... }
}A value type with named fields and optional methods. Structs live on the stack and are passed by value. Fields are public by default and may carry = default values; methods take their receiver as &this, mut &this, or this.
type struct BlockingPool {
mtx: [u8;
40];
cv: [u8;
48];
head: BlockingJob*;
tail: BlockingJob*;
shutdown: boolean;
live: u32;
idle: u32;
max: u32;
// … 1 more
}A pool of OS threads running blocking jobs for one Executor. Heap-allocated: worker threads and submitted jobs hold raw pointers to it, so it must not move.
BlockingPool.mtx: [u8BlockingPool.cv: [u8BlockingPool.head: BlockingJob*type struct BlockingJob {
rc: Atomic<u32>;
status: Atomic<u8>;
waker: Waker;
next: BlockingJob*;
pool: BlockingPool*;
}The header of a blocking job: the erased vtable, the waker to fire when the work finishes, and the refcount that decides who frees the block.
std::future::blockingBlockingPool.tail: BlockingJob*BlockingPool.shutdown: booleanBlockingPool.live: u32BlockingPool.idle: u32BlockingPool.max: u32BlockingPool.tids: u64*static name(param: Type) -> Ret { ... }A method that belongs to the type rather than an instance, called as Type::name(..). For structs, static new(..) returning a struct literal is the idiomatic constructor.
static BlockingPool::start() -> BlockingPool*Create an empty pool with the default thread ceiling.
type struct BlockingPool {
mtx: [u8;
40];
cv: [u8;
48];
head: BlockingJob*;
tail: BlockingJob*;
shutdown: boolean;
live: u32;
idle: u32;
max: u32;
// … 1 more
}A pool of OS threads running blocking jobs for one Executor. Heap-allocated: worker threads and submitted jobs hold raw pointers to it, so it must not move.
static BlockingPool::with_max(max: u32) -> BlockingPool*Create an empty pool that will grow to at most max threads (clamped to >= 1: a pool that could never create a thread would accept jobs and never reach them).
(parameter) max: u32static BlockingPool::stop(p: BlockingPool*) -> voidStop the pool and release everything it owns, leaving the block itself allocated (as Reactor::stop does, and for the same reason: a future dropped later still lets go of a job through this pool).
(parameter) p: BlockingPool*static BlockingPool::free(p: BlockingPool*) -> voidRelease the pool block. Must run only after every future that could still let go of a job has been dropped — see stop.
static BlockingPool::current() -> BlockingPool*The pool serving the current thread's polls, or null if this thread is not an executor worker.
static BlockingPool::set_current(p: BlockingPool*) -> voidPublish p as this thread's pool. Called by an executor worker as it starts, so every future it polls can reach the pool.
BlockingPool::submit(mut &this, job: BlockingJob*) -> booleanQueue job and make sure a thread will reach it, acquiring the pool's reference to the block.
mut &this
mut &TypeAn exclusive, mutating borrow. As a receiver, mut &this lets the method modify the fields - and callers can see that from the signature alone. As a parameter type, mut &T lets the callee mutate through the reference while the caller keeps ownership.
&this
mut &this
thisThe receiver of a method. Its first parameter declares how the method takes it: &this borrows read-only, mut &this borrows exclusively, and this / mut this move the value in and consume it. In the body, this.field reaches the members.
(parameter) job: BlockingJob*BlockingPool::take(mut &this) -> BlockingJob*Block until a job is queued or shutdown is signaled. Returns the dequeued job, or null once shutdown is set — leaving anything still queued for stop to cancel.
type struct BlockingJob {
rc: Atomic<u32>;
status: Atomic<u8>;
waker: Waker;
next: BlockingJob*;
pool: BlockingPool*;
}The header of a blocking job: the erased vtable, the waker to fire when the work finishes, and the refcount that decides who frees the block.
BlockingJob.rc: Atomic<u32>type struct Atomic<T> {
value: T;
}A single atomic cell of type T. See the module docs for the supported T and the compile-time dispatch model.
BlockingJob.status: Atomic<u8>BlockingJob::run_fn: (u8*) -> voidBlockingJob::drop_fn: (u8*) -> voidBlockingJob.waker: Wakertype struct Waker {
data: u8*;
}A handle a stalled future stores so it can be re-scheduled: wake_fn(data) tells the executor "poll my task again". This is the manual-vtable shape used throughout the runtime — non-capturing function pointers plus an opaque data pointer identifying the task.
BlockingJob.next: BlockingJob*BlockingJob.pool: BlockingPool*static BlockingJob::init(job: BlockingJob*, run_fn: (u8*) -> void, drop_fn: (u8*) -> void) -> voidPrepare a freshly allocated block. The refcount starts at 1 — the submitter's own reference — and submit acquires the pool's second one only if it accepts the job, so a refused job needs no unwinding.
(parameter) run_fn: (u8*) -> void(parameter) drop_fn: (u8*) -> voidBlockingJob::arm(mut &this, w: Waker) -> u8Read this job's status and install w as the waker to fire when it finishes. Returns the status as of the moment w was stored.
(parameter) w: WakerBlockingJob::finish(mut &this, status: u8) -> voidPublish status and fire the waker the submitter armed, then release the pool's reference. Called by a pool thread when run_fn returns, and by BlockingPool::stop (with JOB_CANCELLED) for a job no thread reached.
(parameter) status: u8BlockingJob::retain(mut &this) -> voidAcquire one reference to this block, so it cannot be freed while the acquirer still names it. init hands out the first; submit takes the pool's. Pairs one-for-one with release.
BlockingJob::release(mut &this) -> voidDrop one reference to this block. The last one releases any waker still stored and hands the block to its payload's drop_fn, which frees it.
type struct ResolveJobResolveJob.job: BlockingJobResolveJob.host: Stringtype struct String<A = GlobalAlloc> {
buffer: RawBuffer<u8, A>;
length: u64;
}String: owned, heap-allocated, length-typed UTF-8.
std::collections::stringResolveJob.out: Result<..>start..endA half-open range - start up to but excluding end - and sugar for Range::new(start, end): an iterator usable in for, and an ordinary value anywhere else. It binds looser than arithmetic, so a..b + 1 is a..(b + 1).
const NAME: Type = value;At module level, a true compile-time constant, conventionally SCREAMING_SNAKE_CASE. (mut at module level declares mutable global state instead.)
const JOB_PENDING: u8 = 0;const JOB_DONE: u8 = 1;const JOB_CANCELLED: u8 = 2;const BLOCKING_DEFAULT_THREADS: u32 = 512;import a::b;
import a::b::{ X, Y };
import a::b as c;
import a::b::*;Brings a module, or selected items from it, into scope. One path per declaration: use the brace form for several items, as to alias, and * for everything public (prefer the brace form; wildcards invite name collisions).
namespace std::future;std::future: stackless async foundations — Future, Poll, Context, Waker, and the drivers that poll them.
namespace std::future::blocking;std::future::blocking: an off-executor home for work that cannot be polled.
module pagetype struct Executor {
inner: ExecInner*;
worker_tids: [u64];
}A multi-threaded, poll-driven task executor. Construct one, spawn futures onto it (keeping the JoinHandle to await a result, or dropping it to fire-and-forget), and block_on to drive a root future to its value. A pool of worker threads does all polling; the calling thread only ever blocks on a completion condvar.
static BlockingPool::set_current(p: BlockingPool*) -> voidPublish p as this thread's pool. Called by an executor worker as it starts, so every future it polls can reach the pool.
static BlockingPool::current() -> BlockingPool*The pool serving the current thread's polls, or null if this thread is not an executor worker.
std::future::blockingBlockingPool::submit(mut &this, job: BlockingJob*) -> booleanQueue job and make sure a thread will reach it, acquiring the pool's reference to the block.
BlockingPool.mtx: [u8BlockingPool.cv: [u8BlockingPool.head: BlockingJob*BlockingPool.tail: BlockingJob*BlockingPool.shutdown: booleanBlockingPool.live: u32BlockingPool.idle: u32BlockingPool.max: u32BlockingPool.tids: u64*static BlockingPool::start() -> BlockingPool*Create an empty pool with the default thread ceiling.
std::future::blockingstatic BlockingPool::with_max(max: u32) -> BlockingPool*Create an empty pool that will grow to at most max threads (clamped to >= 1: a pool that could never create a thread would accept jobs and never reach them).
static BlockingPool::stop(p: BlockingPool*) -> voidStop the pool and release everything it owns, leaving the block itself allocated (as Reactor::stop does, and for the same reason: a future dropped later still lets go of a job through this pool).
type struct Reactor {
mtx: [u8;
40];
buckets: [Registration*;
64];
poll_h: i64;
kick_h: i64;
aux_h: i64;
shutdown: Atomic<u8>;
thread: u64;
timers: Timer*;
// … 1 more
}The I/O readiness driver for one Executor. Heap-allocated: worker threads and parked futures hold raw pointers to it, and the backend hands the OS interior addresses, so it must not move.
static Reactor::stop(rt: Reactor*) -> voidStop the reactor and release everything it owns, leaving the block itself allocated.
std::future::reactorstatic BlockingPool::free(p: BlockingPool*) -> voidRelease the pool block. Must run only after every future that could still let go of a job has been dropped — see stop.
BlockingPool::take(mut &this) -> BlockingJob*Block until a job is queued or shutdown is signaled. Returns the dequeued job, or null once shutdown is set — leaving anything still queued for stop to cancel.
BlockingJob.rc: Atomic<u32>BlockingJob.status: Atomic<u8>BlockingJob.waker: WakerBlockingJob.next: BlockingJob*BlockingJob.pool: BlockingPool*static BlockingJob::init(job: BlockingJob*, run_fn: (u8*) -> void, drop_fn: (u8*) -> void) -> voidPrepare a freshly allocated block. The refcount starts at 1 — the submitter's own reference — and submit acquires the pool's second one only if it accepts the job, so a refused job needs no unwinding.
BlockingJob::run_fn: (u8*) -> voidBlockingJob::drop_fn: (u8*) -> voidBlockingJob::arm(mut &this, w: Waker) -> u8Read this job's status and install w as the waker to fire when it finishes. Returns the status as of the moment w was stored.
BlockingJob::finish(mut &this, status: u8) -> voidPublish status and fire the waker the submitter armed, then release the pool's reference. Called by a pool thread when run_fn returns, and by BlockingPool::stop (with JOB_CANCELLED) for a job no thread reached.
const JOB_CANCELLED: u8 = 2;BlockingJob::retain(mut &this) -> voidAcquire one reference to this block, so it cannot be freed while the acquirer still names it. init hands out the first; submit takes the pool's. Pairs one-for-one with release.
BlockingJob::release(mut &this) -> voidDrop one reference to this block. The last one releases any waker still stored and hands the block to its payload's drop_fn, which frees it.
static Executor::with_threads_and_blocking(n: u32, blocking: u32) -> ExecutorCreate an executor with n worker threads and a blocking pool that will grow to at most blocking threads.
type struct Command {
program: String;
args: Array<String>;
envs: Array<EnvEntry>;
clear_env_flag: boolean;
cwd: Option<String>;
stdin_cfg: Stdio;
stdout_cfg: Stdio;
stderr_cfg: Stdio;
}Command: builder for spawning a subprocess.
std::process::commandasync Command::collect(&this) -> Result<Output, IoError>Spawn, capture both streams, and await the exit without blocking the calling thread: the async spelling of output.