A barrier phase for a task begins when its pending_cycle token is
set at dispatch and ends when barrier_and_record takes it. The
dispatch path shall submit each task’s borrowed job — the main item
or the fault handler — at most once per phase: a dispatch
requested while the token is already set shall be skipped, never
re-submitted, so that two pool workers can never alias one
*mut dyn FnMut. The token is set uniformly on both the normal and the
fault-routed dispatch branch and for every task kind, so the guard also
covers the borrowed fault-handler submit.
The skipped run is sound because the single run drains all pending
input through its listener take() loop: the listener’s notifications
are level-readable, so a second listener fired in the same wake-phase is
serviced by the one run rather than by a second, aliasing submit. The
now-active observable contract this enforces — once the per-callback
barrier is gone (see below) — is: a task whose multiple listener
triggers all fire in one wake dispatches once per wake, draining every
ready listener in that single run, not once per fired listener.
Consequently each task records at most one cycle and advances
cycle_index at most once per phase. Telemetry stays correct
because record_cycle_for early-returns for event tasks (which carry
no scan_period), so the single recorded cycle is the cyclic one.
This is the explicit guard that makes the at-most-one-submit contract
hold by construction rather than only by accident of the per-callback
barrier. It was the precondition for the barrier-consolidation slice,
which has now landed: the per-callback barrier is removed, and
the dispatch path performs a single barrier_and_record per wake
that covers both the event population (marked by the WaitSet
callback) and the grid/cyclic population (marked by the grid pass),
folding each task’s pending_cycle exactly once. With that
per-callback barrier gone, this guard is now the sole thing holding the
at-most-one-submit contract, so it also closes the latent Grid-path
(Linux-default) hazard whereby run_grid_cyclic_pass barriers only
after its whole due-loop, so two grid slots resolving to one task would
otherwise double-submit the same borrowed job — see
Per-phase dispatch dedup vi... (ADR_0105). It complements the once-per-period cyclic contract of
One execution per scan period (REQ_0002) and the absolute-grid phase-locking of Absolute-grid cyclic dispat... (REQ_0268),
and it must not introduce steady-state allocation on the dispatch path
(No heap allocation in dispatch (REQ_0060)). The per-task lateness and cycle telemetry of
Per-task deadline lateness (REQ_0106) / Per-task scan index and fau... (REQ_0107) therefore see exactly one record per
phase per task.
|