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}