grafos_sync/once.rs
1//! One-time initialization of shared state in fabric memory.
2//!
3//! [`FabricOnce`] ensures a value is computed and stored exactly once in
4//! leased memory, even if multiple callers race to initialize it. The
5//! first writer wins; subsequent callers read the existing value.
6//!
7//! # Memory layout at `base_offset`
8//!
9//! ```text
10//! offset +0: initialized (u8, 1 byte) — 0 = not initialized, 1 = initialized
11//! offset +1: data_len (u32, 4 bytes) — byte length of stored data
12//! offset +5: data ([u8]) — the initialized value
13//! ```
14//!
15//! Total header: 5 bytes before the user data.
16
17extern crate alloc;
18use alloc::vec::Vec;
19
20use grafos_std::error::Result;
21use grafos_std::mem::MemLease;
22
23const INITIALIZED_OFFSET: u64 = 0;
24const DATA_LEN_OFFSET: u64 = 1;
25const DATA_OFFSET: u64 = 5;
26
27/// One-time initialization primitive backed by a fabric memory lease.
28///
29/// Ensures a value is computed and stored exactly once, even if multiple
30/// callers race to initialize it. The first writer wins; subsequent
31/// callers read the existing value.
32///
33/// # Example
34///
35/// ```rust
36/// # grafos_std::host::reset_mock();
37/// # grafos_std::host::mock_set_fbmu_arena_size(65536);
38/// use grafos_sync::FabricOnce;
39/// use grafos_std::mem::MemBuilder;
40///
41/// let lease = MemBuilder::new().acquire().unwrap();
42/// let once = FabricOnce::new(lease, 0).unwrap();
43///
44/// let val = once.call_once(|| b"computed".to_vec()).unwrap();
45/// assert_eq!(&val, b"computed");
46///
47/// // Second call returns the stored value without recomputing
48/// let val2 = once.call_once(|| b"ignored".to_vec()).unwrap();
49/// assert_eq!(&val2, b"computed");
50/// ```
51pub struct FabricOnce {
52 lease: MemLease,
53 base_offset: u64,
54}
55
56impl FabricOnce {
57 /// Create a new `FabricOnce` at `base_offset` in the given lease.
58 ///
59 /// Initializes the `initialized` flag to 0 (not yet initialized).
60 pub fn new(lease: MemLease, base_offset: u64) -> Result<Self> {
61 let mem = lease.mem();
62 mem.write(base_offset + INITIALIZED_OFFSET, &[0u8])?;
63 mem.write(base_offset + DATA_LEN_OFFSET, &0u32.to_le_bytes())?;
64
65 Ok(FabricOnce { lease, base_offset })
66 }
67
68 /// Initialize the value by calling `f`, or return the existing value
69 /// if already initialized.
70 ///
71 /// If the `initialized` flag is already set, `f` is **not** called and
72 /// the stored value is returned. Otherwise, `f` is called, the result
73 /// is written to leased memory (data first, then length, then flag),
74 /// and the value is returned.
75 pub fn call_once<F>(&self, f: F) -> Result<Vec<u8>>
76 where
77 F: FnOnce() -> Vec<u8>,
78 {
79 let mem = self.lease.mem();
80 let base = self.base_offset;
81
82 // Check if already initialized
83 let flag = mem.read(base + INITIALIZED_OFFSET, 1)?;
84 if !flag.is_empty() && flag[0] != 0 {
85 return self.read_stored();
86 }
87
88 // Not initialized — compute the value
89 let data = f();
90 let data_len = data.len() as u32;
91
92 // Write data first, then length, then flag (ordered for crash safety)
93 mem.write(base + DATA_OFFSET, &data)?;
94 mem.write(base + DATA_LEN_OFFSET, &data_len.to_le_bytes())?;
95 mem.write(base + INITIALIZED_OFFSET, &[1u8])?;
96
97 // Double-check: if someone else initialized first, read their value
98 let flag = mem.read(base + INITIALIZED_OFFSET, 1)?;
99 if !flag.is_empty() && flag[0] == 1 {
100 return self.read_stored();
101 }
102
103 Ok(data)
104 }
105
106 /// Returns the lease ID of the underlying memory lease for external
107 /// renewal management (e.g. via [`grafos_leasekit::RenewalManager`]).
108 pub fn lease_id(&self) -> u128 {
109 self.lease.lease_id()
110 }
111
112 /// Returns the expiry time (unix seconds) of the underlying memory
113 /// lease for external renewal management.
114 pub fn expires_at_unix_secs(&self) -> u64 {
115 self.lease.expires_at_unix_secs()
116 }
117
118 /// Check if the value has been initialized.
119 ///
120 /// Returns `true` if [`call_once`](Self::call_once) has completed at least
121 /// once (the `initialized` flag is set in leased memory).
122 pub fn is_initialized(&self) -> Result<bool> {
123 let flag = self
124 .lease
125 .mem()
126 .read(self.base_offset + INITIALIZED_OFFSET, 1)?;
127 Ok(!flag.is_empty() && flag[0] != 0)
128 }
129
130 /// Read the stored value (must be initialized).
131 fn read_stored(&self) -> Result<Vec<u8>> {
132 let mem = self.lease.mem();
133 let base = self.base_offset;
134
135 let len_bytes = mem.read(base + DATA_LEN_OFFSET, 4)?;
136 if len_bytes.len() < 4 {
137 return Ok(Vec::new());
138 }
139 let mut buf = [0u8; 4];
140 buf.copy_from_slice(&len_bytes[..4]);
141 let data_len = u32::from_le_bytes(buf);
142
143 if data_len == 0 {
144 return Ok(Vec::new());
145 }
146
147 mem.read(base + DATA_OFFSET, data_len)
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use grafos_std::host;
155 use grafos_std::mem::MemBuilder;
156
157 fn setup() -> MemLease {
158 host::reset_mock();
159 host::mock_set_fbmu_arena_size(4096);
160 MemBuilder::new().acquire().expect("acquire lease")
161 }
162
163 #[test]
164 fn once_initializes_exactly_once() {
165 let lease = setup();
166 let once = FabricOnce::new(lease, 0).expect("new once");
167
168 assert!(!once.is_initialized().unwrap());
169
170 let mut call_count = 0u32;
171 let val1 = once
172 .call_once(|| {
173 call_count += 1;
174 b"hello fabric".to_vec()
175 })
176 .expect("call_once 1");
177 assert_eq!(call_count, 1);
178 assert_eq!(&val1, b"hello fabric");
179 assert!(once.is_initialized().unwrap());
180
181 // Second call should return existing value without calling f
182 let val2 = once
183 .call_once(|| {
184 call_count += 1;
185 b"should not happen".to_vec()
186 })
187 .expect("call_once 2");
188 assert_eq!(call_count, 1); // f was NOT called again
189 assert_eq!(&val2, b"hello fabric");
190 }
191
192 #[test]
193 fn once_empty_value() {
194 let lease = setup();
195 let once = FabricOnce::new(lease, 0).expect("new once");
196
197 let val = once.call_once(Vec::new).expect("call_once");
198 assert!(val.is_empty());
199 assert!(once.is_initialized().unwrap());
200
201 // Second call returns empty
202 let val2 = once.call_once(|| b"nope".to_vec()).expect("call_once 2");
203 assert!(val2.is_empty());
204 }
205}