pub struct SporadicState {
pub budget_remaining_us: u32,
pub budget_capacity_us: u32,
pub period_us: u32,
pub last_refill_ms: u64,
pub consecutive_budget_skips: u32,
pub window_skips: u32,
pub window_dispatches: u32,
pub max_exec_us: u32,
}Expand description
Phase 110.E — user-space sporadic-server runtime state.
Tracks remaining budget_us for the current period and the wall-
clock instant of the last refill. The executor consults this state
during dispatch: when budget_remaining_us reaches 0 the SC is
suppressed until the next period boundary, at which point a refill
resets the counter.
Refill cadence is polled — each spin_once checks whether the
elapsed time since the last refill exceeds period_us and tops
the budget back up. Less precise than an ISR-driven refill (Phase
110.E’s per-platform timer hook is what gets that) but correct as
an upper-bound bandwidth limiter.
Fields§
§budget_remaining_us: u32§budget_capacity_us: u32§period_us: u32§last_refill_ms: u64§consecutive_budget_skips: u32issue 0736 — consecutive spins in which this SC was skipped for want of
budget. A skip is invisible by construction: spin_once continues
past the entry, so a starved tier looks exactly like an idle one. This
counter is what lets it say so. Reset on any dispatch.
window_skips: u32issue 0736 — skips and dispatches over a rolling window.
The consecutive counter above catches TOTAL starvation quickly and misses the commoner shape entirely: a budget that merely THROTTLES. Measured on nuttx-arm/rust, a 5 ms budget per 10 ms period held the tier to about a quarter of its declared rate — skip a few, refill, dispatch, repeat — so no streak ever reached the consecutive threshold and the tier was silently 4x slow. A declaration that cannot be met has to be reported whether it starves or merely throttles.
window_dispatches: u32§max_exec_us: u32Worst execution time observed on this SC, in microseconds, recorded on EVERY dispatch.
This is the polled path, and issue 0736 established it is the one
that matters: has_budget prefers the atomic state only when a
refill timer was registered, which no board or entry does. So a
number recorded only on the atomic side would be a number no shipped
image ever writes.
Sizing budget_us needs a measured maximum, and the overrun
counters cannot supply one: they say nothing at all until the budget
is already exceeded, which is the wrong end of the question when the
budget is what you are trying to choose.
Only meaningful where the dispatch loop measures: elapsed is 0
unless a latency monitor, a deadline action, or a clock is present.
Implementations§
Source§impl SporadicState
impl SporadicState
Sourcepub const BUDGET_WINDOW: u32 = 200
pub const BUDGET_WINDOW: u32 = 200
How many dispatch opportunities before the throttle ratio is judged. Long enough that a brief burst of skips is not news; short enough to close inside a short run. 1000 was the first value and it NEVER closed on the measured case — that tier misses roughly 240 dispatches over the whole e2e window, so a 1000-opportunity window is larger than the evidence and the warning existed without ever being reachable, which is the same silence it was written to break.
Sourcepub const BUDGET_SKIP_REPORT_PERMILLE: u32 = 250
pub const BUDGET_SKIP_REPORT_PERMILLE: u32 = 250
Report when more than this share of the window was skipped. A quarter is already a tier delivering at 75% of what it declared.
pub const fn new(budget_us: u32, period_us: u32) -> Self
Sourcepub fn refill(&mut self, now_ms: u64) -> bool
pub fn refill(&mut self, now_ms: u64) -> bool
Replenish the budget at period boundaries. Returns true if the SC has
budget remaining afterwards.
issue 0736 — this used to be tick(now_ms, delta_us), which refilled
AND then charged the whole inter-spin delta_us as consumption. A
budget bounds the CPU the SC’s callbacks consume; the wall-clock gap
between two spins is not that, and on any target where the gap exceeds
the budget it exhausts the SC on every single spin no matter what the
callbacks did. Measured on nuttx-arm/rust: delta_us 10_000..80_000
against a 5_000 us budget, giving 1200 budget skips against 3 dispatches
while the sibling tier — identical but declaring no budget — dispatched
on all 450 of its spins.
The delta_us charge was documented as a “worst-case attribution”
standing in for per-callback measurement. That measurement now exists
and runs on every flavour, so consumption belongs to
Self::consume, called with what the callback actually cost.
Sourcepub fn consume(&mut self, us: u32)
pub fn consume(&mut self, us: u32)
Charge measured callback runtime against the remaining budget.
The polled-state twin of AtomicSporadicState::consume. Both exist
because the atomic one is only present when a caller has registered a
refill timer via Executor::register_sporadic_timer — which, outside
this crate’s own tests, nothing does. Every shipped image runs THIS
path, so it is the one that has to be right (issue 0736).
Sourcepub fn take_budget_window(&mut self) -> Option<(u32, u32)>
pub fn take_budget_window(&mut self) -> Option<(u32, u32)>
Close the window if it is full. Returns Some((skips, total)) when the
window closed with a skip share worth reporting.
Sourcepub fn note_budget_skip(&mut self) -> Option<u32>
pub fn note_budget_skip(&mut self) -> Option<u32>
Record one budget-skipped dispatch. Returns Some(n) when the streak
has reached a length worth reporting — at 100, then each power of ten —
so a starved SC is loud once and does not then flood the console.
Trait Implementations§
Source§impl Clone for SporadicState
impl Clone for SporadicState
Source§fn clone(&self) -> SporadicState
fn clone(&self) -> SporadicState
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more