Skip to content
CryoCryo home
Stdlibfuture

reactor

import std::future::reactor; · source

Types

Reactor

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*;
    timer_seq: u64;

    static start() -> Reactor*;
    static stop(rt: Reactor*) -> void;
    static free(rt: Reactor*) -> void;
    static current() -> Reactor*;
    static set_current(rt: Reactor*) -> void;
    static require() -> Reactor*;
    register(mut &this, fd: i32, direction: u32, w: Waker) -> void;
    cancel(mut &this, fd: i32, direction: u32) -> void;
    deregister(mut &this, fd: i32) -> void;
    poll_once(mut &this, timeout_ms: i32) -> void;
    run_once(mut &this) -> void;
    register_timer(mut &this, deadline: i64, w: Waker) -> u64;
    cancel_timer(mut &this, id: u64) -> void;
}

The I/O readiness driver behind every socket future: epoll on Linux, an AFD/IOCP wait on Windows. An Executor starts one and its workers set it as the thread's ambient reactor; you touch it only when writing a new leaf future.

A future whose syscall reports "would block" calls register(fd, direction, waker) with the waker from its Context and returns Pending. The reactor's own thread sits in the OS readiness call until the descriptor is ready, then fires the stored waker, which re-schedules the task; that second poll retries the syscall, which now succeeds.

Two properties of register are correctness requirements, not tuning:

  • One-shot. A delivered event disarms that direction. A future that still cannot finish simply registers again. This is the native shape of a Windows AFD poll and is requested explicitly (EPOLLONESHOT) on Linux.
  • Level-triggered. A descriptor that is already ready at arm time is reported immediately. A future attempts its syscall before it registers, so readiness can arrive in the window between the two; level-triggered arming closes that window, where edge-triggered would park the future forever.

Spurious wakes are always safe: a woken future retries its syscall, and if that still would block it registers again.

cancel(fd, direction) releases one direction's registration without closing anything — what a future's Drop calls — and deregister(fd) removes the descriptor entirely. Timers share the same thread: register_timer(deadline, waker) returns an id that cancel_timer disarms, and the reactor bounds its readiness wait by the earliest deadline, which is how Sleep costs no thread. current() is the thread's ambient reactor; require() panics if there is none, which is the error you see when a socket future is polled outside an executor.

Properties

PropertyType
mtx[u8
40]
buckets[Registration*
64]
poll_hi64
kick_hi64
aux_hi64
shutdownAtomic<u8>
threadu64
timersTimer*
timer_sequ64

Constructors

static start() -> Reactor*;

Create a reactor, open its OS backend, and start its I/O thread. Panics if the OS refuses any of the three: an executor without a working reactor would park I/O futures that could never be woken, so failing loudly at construction beats hanging later.

static current() -> Reactor*;

The reactor driving the current thread's polls, or null if this thread is not an executor worker.

static require() -> Reactor*;

The current thread's reactor, or a panic explaining that async I/O needs one. Called by a future at the moment it is about to park: returning Pending with nowhere to register a waker would hang the task forever, so failing loudly here beats failing silently later.

Static functions

static stop(rt: Reactor*) -> void;

Stop the reactor and release everything it owns, leaving the block itself allocated. Signals shutdown, kicks the thread out of its wait, joins it, then drops every registration and timer still standing — releasing their wakers, which is the last reference to any task still parked on I/O or a deadline, so those tasks are reclaimed rather than leaked.

static free(rt: Reactor*) -> void;

Release the reactor block. Must run only after every future that could still cancel against this reactor has been dropped — see stop.

static set_current(rt: Reactor*) -> void;

Publish rt as this thread's reactor. Called by an executor worker as it starts, so every future it polls can reach the reactor.

Methods

register(mut &this, fd: i32, direction: u32, w: Waker) -> void;

Arm fd for one wake in direction (exactly one INTEREST_* bit) and store w as the waker to fire. Replaces any waker already stored for that direction (a re-registering future supersedes its own earlier one).

cancel(mut &this, fd: i32, direction: u32) -> void;

Disarm one direction on fd, releasing the waker stored for it. Called by an I/O future that is dropped while parked, so cancelling one half of a socket leaves a future waiting on the other half untouched.

deregister(mut &this, fd: i32) -> void;

Drop fd's registration entirely, releasing every waker it held. Called when the descriptor itself goes away — a recycled descriptor number must never inherit a stale registration.

poll_once(mut &this, timeout_ms: i32) -> void;

Wait for readiness (up to timeout_ms; negative blocks indefinitely) and fire the wakers of every descriptor that became ready. The reactor thread calls this in a loop.

run_once(mut &this) -> void;

One iteration of the reactor thread: wait for readiness, but no longer than the earliest armed timer allows, dispatch whatever became ready, then fire every timer that came due. Keeping the wait bound here leaves poll_once a pure "wait at most this long" primitive.

register_timer(mut &this, deadline: i64, w: Waker) -> u64;

Arm a one-shot timer firing w at deadline (monotonic nanoseconds, the scale Instant::as_nanos reports) and return the id that disarms it.

cancel_timer(mut &this, id: u64) -> void;

Disarm the timer with id, releasing the waker it held. A no-op when the timer has already fired or was never armed, which is what lets a dropped Sleep cancel unconditionally without tracking whether it fired.

Constants

const INTEREST_READ:  u32 = 1;
const INTEREST_WRITE: u32 = 2;
const REG_BUCKETS:    i32 = 64;
const RT_MAX_EVENTS:  i32 = 64;