Skip to main content

Module future_exec

Module future_exec 

Source
Expand description

Runtime-free future executor over the callback pool — the RTEMS backend for crate::runtime::task::spawn / crate::runtime::task::spawn_blocking (decision A2, increment W3b).

§Model — cooperative, not worker-per-future

A hosted build runs a spawned async tail as a tokio task. RTEMS has no tokio runtime, so this module runs each spawned future as a task object multiplexed over the CallbackPool:

  1. spawn builds a Task and pushes it onto its band’s ring.
  2. A band worker pops it and polls it once.
  3. Ready → the outcome is published and the task is done.
  4. Pending → the worker releases the task and moves on. The waker handed to the poll is the task itself: waking it pushes the task back onto the ring (at the tail), where some worker picks it up and polls it again.

So a band’s workers are held only for the duration of a single poll, and a bounded worker set multiplexes an unbounded number of mostly-idle tails. All primitives the future awaits must still be runtime-agnostic (tokio::sync locks/channels/notifies, super::timer_sleep) — nothing here drives a reactor or a timer wheel — which is precisely the A2 precondition.

This replaces the original design, where a worker ran park_on_interruptible for the whole life of the future and stayed parked between polls. That model had a structural defect: N concurrent long-lived tails exhausted the band’s N workers, after which every further task on that band starved until one finished. Memory note rtems-exec-worker-per-future-fragility; it mattered more once PVA started sharing this backend.

§Task state machine

One AtomicU8 is the single owner of “may this task be polled, and by whom”:

StateMeaningWho leaves it
IDLEnot queued, not running — waiting for a wakea waker, or an abort
SCHEDULEDsitting on a band ringthe worker that pops it
RUNNINGbeing polled right nowthe polling worker
RUNNING_NOTIFIEDbeing polled, and a wake landed mid-pollthe polling worker (re-enqueues)
DONEterminalnobody

RUNNING_NOTIFIED is what makes a wake that races the poll safe: it is never dropped, it is deferred to the end of the current poll and turned into a re-enqueue. A wake arriving in any other state either enqueues (IDLE) or is redundant (SCHEDULED — already queued; DONE — nothing to run).

§Abort

JoinFuture::abort / AbortHandle::abort latch a flag and then schedule the task, so an idle task is polled once more and observes the flag at the top of that poll — before the future is polled again. That is the same “cancel is observed at the next suspension point” contract the previous park-driver had (there, the abort unparked the parked worker); only the wake-up mechanism changed.

§Handle surface

Unchanged. JoinFuture<T> mirrors the subset of tokio::task::JoinHandle the CA/PVA call sites actually use (see the W3b call-site map): impl Future<Output = Result<T, JoinError>>, abort, is_finished, and abort_handle returning a non-generic AbortHandle with abort / is_finished. JoinError mirrors only is_cancelled — the one JoinError method any call site consumes.

§Every handle resolves

Shared::finalize is the single owner of “this task has an outcome”, and it is idempotent. Three paths reach it, covering every way a task can stop existing: the poll produced Ready/cancel/panic; the queued entry was dropped without ever running (ring full, or the band shut down under it); or the task became unreachable — no queue entry and no live waker — and its Drop ran. A JoinFuture therefore never strands.

§Panic isolation

C callbackTask (callback.c:210-235, cited in super::callback_executor) is a bare drain loop: it calls each callback and loops, with no exception machinery (C has none). A Rust callback can unwind, and an unwind out of the worker closure would tear down the band’s worker thread — breaking that drain-loop invariant. So each poll is run under catch_unwind: a panicking task is reported as a panicked JoinError and the worker keeps draining, preserving the C loop’s “one callback never stops the worker” property.

Structs§

AbortHandle
A cancellation handle detached from a JoinFuture — the mirror of tokio::task::AbortHandle. Cloneable and non-generic, so it can be stored in heterogeneous collections (as ca/pva store AbortHandles).
JoinError
Why awaiting a JoinFuture yielded an error instead of the task output — the seam-owned mirror of tokio::task::JoinError.
JoinFuture
A handle over a spawned task — the RTEMS-side mirror of tokio::task::JoinHandle. await it for Result<T, JoinError>.

Constants§

DEFAULT_SPAWN_PRIORITY
Default band for a general spawned tail. C routes general deferred work through callbackRequest at priorityMedium (callback.h:42) — the middle of the three bands (callback.h:41-43) — so a spawned async tail lands there unless a caller picks another band.

Functions§

spawn_blocking_on
Run a blocking closure f on a callback-pool worker — the RTEMS backend for crate::runtime::task::spawn_blocking. Returns a JoinFuture resolving to f’s return value.
spawn_future
Spawn fut onto the callback pool behind callbacks, polled on priority-band workers. Returns immediately with a JoinFuture.