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:
spawnbuilds aTaskand pushes it onto its band’s ring.- A band worker pops it and polls it once.
Ready→ the outcome is published and the task is done.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”:
| State | Meaning | Who leaves it |
|---|---|---|
IDLE | not queued, not running — waiting for a wake | a waker, or an abort |
SCHEDULED | sitting on a band ring | the worker that pops it |
RUNNING | being polled right now | the polling worker |
RUNNING_NOTIFIED | being polled, and a wake landed mid-poll | the polling worker (re-enqueues) |
DONE | terminal | nobody |
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§
- Abort
Handle - A cancellation handle detached from a
JoinFuture— the mirror oftokio::task::AbortHandle. Cloneable and non-generic, so it can be stored in heterogeneous collections (as ca/pva storeAbortHandles). - Join
Error - Why awaiting a
JoinFutureyielded an error instead of the task output — the seam-owned mirror oftokio::task::JoinError. - Join
Future - A handle over a spawned task — the RTEMS-side mirror of
tokio::task::JoinHandle.awaitit forResult<T, JoinError>.
Constants§
- DEFAULT_
SPAWN_ PRIORITY - Default band for a general spawned tail. C routes general deferred work
through
callbackRequestatpriorityMedium(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
fon a callback-pool worker — the RTEMS backend forcrate::runtime::task::spawn_blocking. Returns aJoinFutureresolving tof’s return value. - spawn_
future - Spawn
futonto the callback pool behindcallbacks, polled onpriority-band workers. Returns immediately with aJoinFuture.