proxima_clock/coarse.rs
1use core::convert::Infallible;
2use core::fmt;
3use core::future::Future;
4
5use proxima_primitives::pipe::primitives::Pipe;
6
7use crate::seq_u64s::SeqU64s;
8use crate::ticks::Ticks;
9
10/// A shared, coarsely-updated tick count — the common per-core/per-shard
11/// "what time is it, roughly" cell, distinct from a per-read hardware
12/// register.
13///
14/// Some deployments do not want every caller reading a hardware counter
15/// directly (a syscall-backed clock, a register behind a slow bus, a
16/// counter shared across cores where every core hitting the same
17/// cache line would thrash it). The usual answer is one owner reads the
18/// real source occasionally and writes the result here; every other
19/// reader reads this cell instead — cheap, lock-free, and no less
20/// accurate than the owner's update cadence.
21///
22/// `TickCell` reads exactly like a hardware counter: `impl Pipe for
23/// &TickCell` gives it the same `In = (), Out = Ticks, Err = Infallible`
24/// source shape as a `read_volatile` on a memory-mapped register (see
25/// `tests/hardware_mmap_seam.rs`, which drives both through one
26/// pipeline) — a caller composing a pipeline does not know or care
27/// whether a given tick source is a raw register or this cell.
28///
29/// Deliberately NOT a process-wide `static` here: how a cell is reached
30/// (a field on a per-core context, threaded through a runtime handle, a
31/// `static` a caller chooses to declare) is a runtime-wiring decision
32/// this `no_std` leaf crate does not make on the caller's behalf.
33///
34/// # Contract
35///
36/// Single writer: [`TickCell::set`] is for the one owner that reads the
37/// real source and republishes it. Concurrent `set` calls from more than
38/// one caller are not synchronized against each other — the underlying
39/// seqlock's odd/even bump is not itself compare-and-swapped. Any number
40/// of readers may call [`TickCell::get`] (or drive it as a `Pipe`)
41/// concurrently, lock-free.
42pub struct TickCell {
43 ticks: SeqU64s<1>,
44}
45
46impl TickCell {
47 /// Construct a cell starting at `initial`.
48 #[must_use]
49 pub fn new(initial: Ticks) -> Self {
50 Self {
51 ticks: SeqU64s::new([initial.as_raw()]),
52 }
53 }
54
55 /// Republish the current tick count. See the struct doc's
56 /// single-writer contract.
57 pub fn set(&self, ticks: Ticks) {
58 self.ticks.store([ticks.as_raw()]);
59 }
60
61 /// Read the current tick count.
62 #[must_use]
63 pub fn get(&self) -> Ticks {
64 let [raw] = self.ticks.load();
65 Ticks::from_raw(raw)
66 }
67}
68
69// not derived: a derived impl reads each atomic half on its own, so a
70// `{:?}` racing the writer can print a value that was never stored.
71impl fmt::Debug for TickCell {
72 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73 formatter
74 .debug_struct("TickCell")
75 .field("ticks", &self.get())
76 .finish()
77 }
78}
79
80impl Pipe for &TickCell {
81 type In = ();
82 type Out = Ticks;
83 type Err = Infallible;
84
85 fn call(&self, (): ()) -> impl Future<Output = Result<Ticks, Infallible>> {
86 let ticks = self.get();
87 async move { Ok(ticks) }
88 }
89}
90
91#[cfg(test)]
92#[allow(clippy::unwrap_used, clippy::expect_used)]
93mod tests {
94 use super::TickCell;
95 use crate::ticks::Ticks;
96 use proxima_primitives::block_on;
97 use proxima_primitives::pipe::primitives::Pipe;
98
99 #[test]
100 fn set_then_get_round_trips() {
101 let cell = TickCell::new(Ticks::from_raw(1_000));
102
103 cell.set(Ticks::from_raw(2_000));
104
105 assert_eq!(cell.get(), Ticks::from_raw(2_000));
106 }
107
108 #[test]
109 fn reads_as_a_source_pipe() {
110 let cell = TickCell::new(Ticks::from_raw(24_000_000));
111
112 let read = block_on(Pipe::call(&&cell, ())).expect("cell reads never fail");
113
114 assert_eq!(read, Ticks::from_raw(24_000_000));
115 }
116
117 // `format!` needs an allocator, so the rendering assertions are std-tier;
118 // the `Debug` impl itself is `core`-only and compiles on the floor.
119 #[cfg(feature = "std")]
120 mod rendering {
121 use super::TickCell;
122 use crate::ticks::Ticks;
123
124 #[test]
125 fn debug_renders_the_live_value_not_the_constructed_one() {
126 let cell = TickCell::new(Ticks::from_raw(1_000));
127
128 cell.set(Ticks::from_raw(2_000));
129
130 assert_eq!(format!("{cell:?}"), "TickCell { ticks: Ticks(2000) }");
131 }
132 }
133}