pub struct ConfigCell<T, const WAITERS: usize = { crate::DEFAULT_WAITERS }> { /* private fields */ }Expand description
Process-wide storage for one configuration type.
Lives in a static, which is the only place a device has to put anything
that outlives a function:
static SETTINGS: ConfigCell<Settings> = ConfigCell::new();§Why a critical section
A reader has to see either the old configuration or the new one, never a
mixture. On a host that is an ArcSwap; on a device without an allocator it
is a few instructions with interrupts masked, which is what
critical_section provides and what every embedded HAL implements.
The section is held for a clone of the value and nothing else. Keep the configuration struct small — which a device’s configuration is — and that is a memcpy with interrupts off, measured in microseconds.
Implementations§
Source§impl<T, const WAITERS: usize> ConfigCell<T, WAITERS>
impl<T, const WAITERS: usize> ConfigCell<T, WAITERS>
Sourcepub fn waiter_evictions(&self) -> u32
Available on crate feature async only.
pub fn waiter_evictions(&self) -> u32
async only.How many times a waiter has had to displace another one, saturating.
A ConfigCell parks WAITERS tasks and no more. Past that, a
registration evicts an existing waiter and wakes it — no wake-up is
lost, but the two tasks then wake each other for as long as both are
waiting, and a device that is doing that is not asleep. There is no
fifth slot to find: a fixed array cannot park what does not fit, and
the alternatives (drop the waker, refuse the registration) are both a
task that nobody polls again.
So this is the report. Non-zero means WAITERS is too small for this
firmware — raise the second type parameter to the number of tasks
that genuinely await this configuration. Zero on a device that has run
its real workload means the budget fits, which is the only proof of
that worth having.
static SETTINGS: ConfigCell<Settings, 4> = ConfigCell::new();
// On a bench, after the firmware has run everything it does:
assert_eq!(SETTINGS.waiter_evictions(), 0, "raise WAITERS");Source§impl<T: Clone, const WAITERS: usize> ConfigCell<T, WAITERS>
impl<T: Clone, const WAITERS: usize> ConfigCell<T, WAITERS>
Sourcepub fn store(&self, value: T)
pub fn store(&self, value: T)
Installs value, replacing whatever was there.
For compiled-in defaults at start-up, and for anything that builds a configuration without parsing one.
Source§impl<T: Clone + DeserializeOwned + Validate, const WAITERS: usize> ConfigCell<T, WAITERS>
impl<T: Clone + DeserializeOwned + Validate, const WAITERS: usize> ConfigCell<T, WAITERS>
Sourcepub fn apply(&self, document: &[u8], format: Format) -> Result<(), Error>
pub fn apply(&self, document: &[u8], format: Format) -> Result<(), Error>
Parses document and installs it, if it is usable.
Everything that can fail happens before anything is installed: a document that does not parse, does not fit, or does not validate leaves the previous configuration serving. That is the whole reason this is one call rather than parse-then-store.
§Errors
If the bytes are not valid in format, do not fit T, or T rejects
itself.