Skip to content
CryoCryo home
Stdlibfuture

reactor

import std::future::reactor; · source

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.