Skip to main content

SporadicState

Struct SporadicState 

Source
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: u32

issue 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: u32

issue 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: u32

Worst 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

Source

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.

Source

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.

Source

pub const fn new(budget_us: u32, period_us: u32) -> Self

Source

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.

Source

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).

Source

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.

Source

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

Source§

fn clone(&self) -> SporadicState

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for SporadicState

Source§

impl Debug for SporadicState

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.