1 unstable release
0.1.0 | May 5, 2024 |
---|
#1046 in Embedded development
180KB
1.5K
SLoC
Cheap value sharing and change notification for lilos
This crate provides the Watch<T>
data structure for sharing data with update
notifications. See the crate docs for details.
lib.rs
:
A simple mechanism for sharing a piece of data and being notified if it changes.
This module provides Watch<T>
, a data structure that lets you share some
data of type T
, update it from multiple producers, and efficiently notify
multiple consumers when it changes.
A Watch<T>
is particularly useful for things like configuration data,
which are effectively "global" (shared by many tasks) but may need special
handling of changes.
let shared_number = Watch::new(1234_u32);
You can create a receive handle to the Watch<T>
by calling
subscribe
. This produces a Receiver<T>
that allows
its holder to inspect the shared data, and be notified when it changes.
let rcvr = shared_number.subscribe();
// A receiver only tracks changes made _after_ it's created:
assert_eq!(rcvr.is_changed(), false);
You can create a send handle to the Watch<T>
by calling
sender
. This produces a Sender<T>
that allows its
holder to update the shared data.
let sender = shared_number.sender();
// Update the shared data:
sender.send(4567);
// Now the receiver sees a change:
assert_eq!(rcvr.is_changed(), true);
// We can inspect it and mark it as seen:
rcvr.glimpse_and_update(|value| assert_eq!(*value, 4567));
Code that wants to monitor changes to the data can use the
Receiver::changed
future to do so:
loop {
rcvr.changed().await;
rcvr.glimpse_and_update(|value| process(value));
}
Reentrancy and panics
It is possible to attempt to use handles for a single Watch<T>
reentrantly
if you try hard enough. The implementation checks for this and will panic.
For instance, attempting to send a new value from inside the closure
passed to Receiver::glimpse
:
let shared = Watch::new(some_data);
let sender = shared.sender();
let rcvr = shared.subscribe();
// This line will panic at runtime.
rcvr.glimpse(|contents| sender.send(*contents));
It is perfectly safe to send or inspect a different Watch<T>
instance
from within one of these closures, just not the same one.
In practice it's pretty hard to do this accidentally, but, now you know.
Implementation
Specifically, the Watch<T>
contains a change count. Each time its
contents are updated by any Sender
, the change count gets incremented.
Each Receiver
has its own copy of the change count, reflecting what the
count was at the last time that Receiver
accessed the shared data. If the
change count stored in the Watch
is different from the one stored in the
Receiver
, then there is an update that hasn't been seen by its holder yet.
Because the Watch<T>
only stores a single copy of the data and a counter,
a Receiver
may not see every update to the data. If multiple updates
happen between checks, the Receiver
will only ever see the last one. This
keeps both the storage requirements, and the cost of updates, low.
Watch<T>
contains a Notify
internally, which it uses to efficiently
wake tasks that are awaiting Receiver::changed
.
Dependencies
~3.5MB
~61K SLoC