pub struct AtomicSporadicState {
pub budget_remaining_us: AtomicU32,
pub last_refill_ms: AtomicU32,
pub budget_capacity_us: u32,
pub period_us: u32,
pub overrun_count: AtomicU32,
pub last_overrun_us: AtomicU32,
pub max_exec_us: AtomicU32,
}Expand description
Phase 110.E.b — atomic sporadic-server state for ISR-driven
refill. ISR / timer-thread context calls refill_thunk to top up
the budget; spin_once reads atomically without any &mut access.
Replaces the polled-clock SporadicState shape on platforms with
a PlatformTimer impl. The Executor still keeps the legacy
SporadicState path active on feature = "std" so the
transition is non-breaking.
Fields§
§budget_remaining_us: AtomicU32§last_refill_ms: AtomicU32Wraps every ~50 days at ms resolution; saturates per
tick’s monotonic-clock contract. portable-atomic provides
AtomicU32 even on RISC-V riscv32imc / Cortex-M0+ that lack
native 32-bit atomics.
budget_capacity_us: u32§period_us: u32§overrun_count: AtomicU32Phase 110.E.b — cumulative count of dispatched callbacks
whose measured wall-clock runtime exceeded the SC’s
budget_us. Bumped by the per-callback runtime closure
inside Executor::spin_once (std-only — the no_std fallback
continues to use the polled SporadicState path without
per-callback overrun accounting). Cooperative single-thread
dispatch can’t preempt a runaway callback, so this counter
is the diagnostic signal — the design’s oneshot-IRQ-and-
cancel pattern is structurally equivalent for non-preemptive
callbacks, and last_overrun_us carries the worst-case
observation for tuning. Both reset by clear_overrun_stats.
last_overrun_us: AtomicU32WORST overrun amount seen (measured_us - budget_us) since the
last clear_overrun_stats; 0 when none has been observed.
This used to store the most RECENT overrun while its doc claimed
it “carries the worst-case observation for tuning”. A large overrun
followed by a small one silently replaced the number anyone would
have sized a budget from, and the only surviving evidence of the bad
dispatch was that overrun_count had gone up by one. It is a
fetch_max now, which is what the name and the purpose both say.
max_exec_us: AtomicU32Worst execution time observed on this SC, in microseconds, recorded on EVERY dispatch rather than only on an overrun.
overrun_count and last_overrun_us answer “did it exceed the
budget, and by how much”. Neither can answer “how close did it come”
— a system that never overruns produces no evidence at all, and that
is exactly the system whose budget you are trying to choose. Sizing
budget_us from a measured maximum is the whole point of the field.
Only meaningful where the dispatch loop actually measures: elapsed is
0 unless a latency monitor, a deadline action, or a clock is
present (measure_us in spin_once). No clock, no number.
Implementations§
Source§impl AtomicSporadicState
impl AtomicSporadicState
pub const fn new(budget_us: u32, period_us: u32) -> Self
Sourcepub fn record_overrun(&self, overrun_us: u32)
pub fn record_overrun(&self, overrun_us: u32)
Record one overrun: callback measured runtime exceeded the
SC’s budget_us. Bumps overrun_count + stores the absolute
overrun amount in last_overrun_us. Called from the
per-callback runtime closure inside Executor::spin_once.
Sourcepub fn record_exec(&self, elapsed_us: u32)
pub fn record_exec(&self, elapsed_us: u32)
Record one dispatch’s measured execution time. Called on EVERY dispatch charged to this SC, overrun or not.
Sourcepub fn max_exec_us(&self) -> u32
pub fn max_exec_us(&self) -> u32
Worst execution time seen since the last clear_overrun_stats.
Sourcepub fn clear_overrun_stats(&self)
pub fn clear_overrun_stats(&self)
Reset both overrun statistics. Useful when tuning the budget across windows (monitoring code logs + clears periodically).
Sourcepub fn has_budget(&self) -> bool
pub fn has_budget(&self) -> bool
Read the budget atomically; spin_once consults this to decide whether to skip the SC’s entries this cycle.