Skip to main content

Module ring_executor

Module ring_executor 

Source
Expand description

RingExecutor: an async executor whose READY QUEUE is built from SubEtha rings. The future’s handle rides through a ring; the ring IS the scheduler, not a data channel beside one.

This is the deeper async shape than crate::waker_ring. There the ring carries message bytes and the future lives in a heap queue (Mutex<VecDeque> in crate::task_pool). Here the future’s Arc<Task> handle is the ring payload: a worker pops a handle, reconstructs the Arc, polls it; a wake() pushes the handle back into a ring. Scheduling = a ring push; running = a ring pop.

§Shape-adaptive ready queue

The ready queue is NOT one ring. A single Vyukov ring funnels every core’s CAS through one counter and walls throughput as cores climb (Vyukov contention rises sharply past a handful of producers). So the executor shards the ready queue to the hardware: ONE ready-ring shard per worker, worker count taken from std::thread::available_parallelism (or supplied explicitly). Each worker owns a home shard, drains it first, and STEALS from the other shards round-robin when its own is empty. A task is homed to one shard round-robin at spawn and always reschedules there, so a self-waking task’s handle stays on one ring (locality) and the home worker is almost always the only thread touching it. The ring count equals the core count: 1 core -> 1 ring, a 44-thread host -> 44 rings, no single-counter wall.

Workers pin to distinct cores best-effort (crate::cpu_affinity); on a host without an affinity API they run unpinned.

§Why this answers “uncapped consumers”

WORKERS are the hardware parallelism - a small, fixed cap matched to the machine. TASKS are unbounded: they are Arc<Task> handles multiplexed onto the worker pool through the rings, not threads. A 44-thread host drives an arbitrary task population on 44 workers.

§Handle / refcount discipline

A task is in its home ring at most once, gated by a scheduled flag:

  • spawn / wake flip scheduled false->true and push one Arc::into_raw handle. A redundant wake (flag already true) drops its clone instead of double-pushing.
  • a worker pops a handle, Arc::from_raw reclaims that ref, clears scheduled, and polls. Ready drops the future and the run-ref; Pending drops the run-ref, leaving the future’s stashed waker clone as the liveness anchor until the next wake re-pushes.

Because each live task occupies at most one slot of its home shard, a shard sized to >= peak tasks homed there never returns Full; the round-robin home spread keeps that at about peak_tasks / shards. A Full push (only possible under an adversarial liveness/home correlation) spins until a stealer drains the shard, which is deadlock-free whenever more than one worker runs.

Structs§

RingExecutor
A hardware-shaped pool of worker threads draining an unbounded set of tasks through per-worker SubEtha ready-ring shards.