1use std::fs::{File, OpenOptions};
18use std::marker::PhantomData;
19use std::mem::{align_of, size_of};
20use std::path::Path;
21use std::sync::atomic::{AtomicU8, Ordering};
22
23use memmap2::{MmapMut, MmapOptions};
24
25pub const ONCE_MAGIC: u32 = 0x4F4E_4346;
26pub const ONCE_PAYLOAD_BYTES: usize = 56;
27
28pub const STATE_EMPTY: u8 = 0;
29pub const STATE_INITIALIZING: u8 = 1;
30pub const STATE_INITIALIZED: u8 = 2;
31
32#[repr(C, align(64))]
33pub struct OnceHeader {
34 pub magic: u32,
35 pub size: u32,
36 pub state: AtomicU8,
37 pub _pad_to_payload: [u8; 7],
38 pub payload: [u8; ONCE_PAYLOAD_BYTES],
39}
40
41pub const ONCE_FILE_SIZE: usize = size_of::<OnceHeader>();
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum SharedOnceError {
45 LayoutMismatch,
46 PayloadTooLarge,
47 IoError(std::io::ErrorKind),
48}
49
50impl From<std::io::Error> for SharedOnceError {
51 fn from(e: std::io::Error) -> Self { Self::IoError(e.kind()) }
52}
53
54pub struct SharedOnceCell<T: Copy + 'static> {
55 _file: File,
56 mmap: MmapMut,
57 _phantom: PhantomData<T>,
58 header_sidecar: subetha_core::HandshakeHeader,
59 ring_sidecar: Box<subetha_core::ObservationRing>,
60}
61
62unsafe impl<T: Copy + Send + 'static> Send for SharedOnceCell<T> {}
63unsafe impl<T: Copy + Sync + 'static> Sync for SharedOnceCell<T> {}
64
65impl<T: Copy + Send + Sync + 'static> subetha_sidecar::AdaptiveInstance for SharedOnceCell<T> {
66 fn header(&self) -> &subetha_core::HandshakeHeader { &self.header_sidecar }
67 fn ring(&self) -> &subetha_core::ObservationRing { &self.ring_sidecar }
68 fn make_policy(&self) -> Box<dyn subetha_sidecar::Policy> {
69 Box::new(subetha_sidecar::NoMigrationPolicy)
70 }
71}
72
73impl<T: Copy + 'static> SharedOnceCell<T> {
74 pub fn create(path: impl AsRef<Path>) -> Result<Self, SharedOnceError> {
80 Self::check_layout()?;
81 let (file, mmap) = crate::mmf_attach::create_or_attach(
82 path.as_ref(),
83 ONCE_FILE_SIZE,
84 |ptr| unsafe { Self::init_region(ptr) },
85 |ptr| unsafe { (*(ptr as *const OnceHeader)).magic == ONCE_MAGIC },
86 )?;
87 Self::from_region(file, mmap)
88 }
89
90 pub fn reset(path: impl AsRef<Path>) -> Result<Self, SharedOnceError> {
94 Self::check_layout()?;
95 let (file, mmap) = crate::mmf_attach::reset(path.as_ref(), ONCE_FILE_SIZE, |ptr| unsafe {
96 Self::init_region(ptr)
97 })?;
98 Self::from_region(file, mmap)
99 }
100
101 unsafe fn init_region(ptr: *mut u8) {
109 let hdr = ptr as *mut OnceHeader;
110 unsafe {
111 (*hdr).size = size_of::<T>() as u32;
112 std::ptr::write_volatile(&raw mut (*hdr).magic, ONCE_MAGIC);
113 }
114 }
115
116 fn from_region(file: File, mmap: MmapMut) -> Result<Self, SharedOnceError> {
119 let header = unsafe { &*(mmap.as_ptr() as *const OnceHeader) };
120 if header.magic != ONCE_MAGIC || header.size as usize != size_of::<T>() {
121 return Err(SharedOnceError::LayoutMismatch);
122 }
123 Ok(Self {
124 _file: file, mmap, _phantom: PhantomData,
125 header_sidecar: subetha_core::HandshakeHeader::new(),
126 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
127 })
128 }
129
130 pub fn open(path: impl AsRef<Path>) -> Result<Self, SharedOnceError> {
131 Self::check_layout()?;
132 let file = OpenOptions::new().read(true).write(true).open(path.as_ref())?;
133 if file.metadata()?.len() < ONCE_FILE_SIZE as u64 {
134 return Err(SharedOnceError::LayoutMismatch);
135 }
136 let mmap = unsafe { MmapOptions::new().len(ONCE_FILE_SIZE).map_mut(&file)? };
137 Self::from_region(file, mmap)
138 }
139
140 fn check_layout() -> Result<(), SharedOnceError> {
141 if size_of::<T>() > ONCE_PAYLOAD_BYTES {
142 return Err(SharedOnceError::PayloadTooLarge);
143 }
144 if align_of::<T>() > 8 {
145 return Err(SharedOnceError::PayloadTooLarge);
146 }
147 Ok(())
148 }
149
150 fn header(&self) -> &OnceHeader {
151 unsafe { &*(self.mmap.as_ptr() as *const OnceHeader) }
152 }
153
154 pub fn is_initialized(&self) -> bool {
156 self.header().state.load(Ordering::Acquire) == STATE_INITIALIZED
157 }
158
159 pub fn get(&self) -> Option<T> {
162 let header = self.header();
163 if header.state.load(Ordering::Acquire) != STATE_INITIALIZED {
164 self.ring_sidecar
165 .push_op(crate::sidecar_ops::cell::OP_GET, 2); return None;
167 }
168 let value: T = unsafe {
169 let src = header.payload.as_ptr() as *const T;
170 std::ptr::read_unaligned(src)
171 };
172 self.ring_sidecar
173 .push_op(crate::sidecar_ops::cell::OP_GET, 0);
174 Some(value)
175 }
176
177 pub fn set(&self, value: T) -> bool {
181 let header = self.header();
182 if header.state.compare_exchange(
183 STATE_EMPTY, STATE_INITIALIZING,
184 Ordering::AcqRel, Ordering::Acquire,
185 ).is_err() {
186 self.ring_sidecar
187 .push_op(crate::sidecar_ops::cell::OP_SET, 1); return false;
189 }
190 unsafe {
191 let dst = header.payload.as_ptr() as *mut T;
192 std::ptr::write_unaligned(dst, value);
193 }
194 header.state.store(STATE_INITIALIZED, Ordering::Release);
195 self.ring_sidecar
196 .push_op(crate::sidecar_ops::cell::OP_SET, 0);
197 true
198 }
199
200 pub fn get_or_init<F: FnOnce() -> T>(&self, init: F) -> T {
204 if let Some(v) = self.get() { return v; }
205 let header = self.header();
206 match header.state.compare_exchange(
207 STATE_EMPTY, STATE_INITIALIZING,
208 Ordering::AcqRel, Ordering::Acquire,
209 ) {
210 Ok(_) => {
211 let v = init();
213 unsafe {
214 let dst = header.payload.as_ptr() as *mut T;
215 std::ptr::write_unaligned(dst, v);
216 }
217 header.state.store(STATE_INITIALIZED, Ordering::Release);
218 self.ring_sidecar
219 .push_op(crate::sidecar_ops::cell::OP_SET, 0);
220 v
221 }
222 Err(_) => {
223 while header.state.load(Ordering::Acquire) != STATE_INITIALIZED {
225 std::hint::spin_loop();
226 }
227 self.get().expect("INITIALIZED implies value present")
228 }
229 }
230 }
231
232 pub fn flush_async(&self) -> Result<(), SharedOnceError> {
236 self.mmap.flush_async()?;
237 Ok(())
238 }
239
240 pub fn flush(&self) -> Result<(), SharedOnceError> {
241 self.mmap.flush()?;
242 Ok(())
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 fn tmp(name: &str) -> std::path::PathBuf {
251 let mut p = std::env::temp_dir();
252 let pid = std::process::id();
253 p.push(format!("subetha-once-{name}-{pid}.bin"));
254 p
255 }
256
257 #[test]
258 fn fresh_cell_is_empty() {
259 let p = tmp("fresh");
260 let c: SharedOnceCell<u64> = SharedOnceCell::create(&p).unwrap();
261 assert!(!c.is_initialized());
262 assert_eq!(c.get(), None);
263 std::fs::remove_file(&p).ok();
264 }
265
266 #[test]
267 fn first_set_wins_subsequent_sets_lose() {
268 let p = tmp("first-wins");
269 let c: SharedOnceCell<u64> = SharedOnceCell::create(&p).unwrap();
270 assert!(c.set(42));
271 assert!(!c.set(99));
272 assert_eq!(c.get(), Some(42));
273 std::fs::remove_file(&p).ok();
274 }
275
276 #[test]
280 fn second_create_attaches_and_keeps_the_value() {
281 let p = tmp("attach");
282 std::fs::remove_file(&p).ok();
283 let c: SharedOnceCell<u64> = SharedOnceCell::create(&p).unwrap();
284 assert!(c.set(42));
285
286 let c2: SharedOnceCell<u64> = SharedOnceCell::create(&p).unwrap();
287 assert_eq!(c2.get(), Some(42), "attach lost the initialized value");
288 assert!(!c2.set(99), "attach reopened a spent cell");
289
290 drop(c);
293 drop(c2);
294 let fresh: SharedOnceCell<u64> = SharedOnceCell::reset(&p).unwrap();
295 assert_eq!(fresh.get(), None, "reset left a value behind");
296 assert!(fresh.set(7));
297 drop(fresh);
298 std::fs::remove_file(&p).ok();
299 }
300
301 #[test]
303 fn create_refuses_a_mismatched_region() {
304 let p = tmp("mismatch");
305 std::fs::remove_file(&p).ok();
306 let c: SharedOnceCell<u64> = SharedOnceCell::create(&p).unwrap();
307 assert!(matches!(
308 SharedOnceCell::<u32>::create(&p),
309 Err(SharedOnceError::LayoutMismatch),
310 ));
311 drop(c);
312 std::fs::remove_file(&p).ok();
313 }
314
315 #[test]
316 fn cross_handle_init_visible_to_other_handle() {
317 let p = tmp("cross-handle");
318 let writer: SharedOnceCell<u64> = SharedOnceCell::create(&p).unwrap();
319 let reader: SharedOnceCell<u64> = SharedOnceCell::open(&p).unwrap();
320 assert!(!reader.is_initialized());
321 writer.set(7777);
322 assert!(reader.is_initialized());
323 assert_eq!(reader.get(), Some(7777));
324 std::fs::remove_file(&p).ok();
325 }
326
327 #[test]
328 fn get_or_init_runs_closure_at_most_once() {
329 use std::sync::Arc;
330 use std::sync::atomic::AtomicU32;
331 use std::thread;
332 let p = tmp("get-or-init");
333 let c: Arc<SharedOnceCell<u64>> = Arc::new(SharedOnceCell::create(&p).unwrap());
334 let runs = Arc::new(AtomicU32::new(0));
335 let mut handles = vec![];
336 for _ in 0..8 {
337 let c = c.clone();
338 let runs = runs.clone();
339 handles.push(thread::spawn(move || {
340 c.get_or_init(|| {
341 runs.fetch_add(1, Ordering::AcqRel);
342 1234u64
343 })
344 }));
345 }
346 let results: Vec<u64> = handles.into_iter().map(|h| h.join().unwrap()).collect();
347 assert!(results.iter().all(|v| *v == 1234));
348 assert_eq!(runs.load(Ordering::Acquire), 1,
349 "init closure must run exactly once across 8 threads");
350 std::fs::remove_file(&p).ok();
351 }
352
353 #[test]
354 fn disk_persistence_survives_reopen() {
355 let p = tmp("disk-persist");
356 {
357 let c: SharedOnceCell<u64> = SharedOnceCell::create(&p).unwrap();
358 c.set(8888);
359 c.flush().unwrap();
360 }
361 let c2: SharedOnceCell<u64> = SharedOnceCell::open(&p).unwrap();
362 assert_eq!(c2.get(), Some(8888));
363 assert!(c2.is_initialized());
364 assert!(!c2.set(9999));
366 assert_eq!(c2.get(), Some(8888));
367 std::fs::remove_file(&p).ok();
368 }
369
370 #[test]
371 fn payload_too_large_at_create() {
372 #[allow(dead_code)] struct Big([u8; ONCE_PAYLOAD_BYTES + 1]);
374 impl Copy for Big {}
375 impl Clone for Big { fn clone(&self) -> Self { *self } }
376 let p = tmp("too-large");
377 match SharedOnceCell::<Big>::create(&p) {
378 Err(SharedOnceError::PayloadTooLarge) => {}
379 other => panic!("expected PayloadTooLarge, got {:?}", other.as_ref().err()),
380 }
381 std::fs::remove_file(&p).ok();
382 }
383
384 #[test]
385 fn open_rejects_wrong_payload_size() {
386 let p = tmp("wrong-size");
387 let _c: SharedOnceCell<u64> = SharedOnceCell::create(&p).unwrap();
388 match SharedOnceCell::<u32>::open(&p) {
389 Err(SharedOnceError::LayoutMismatch) => {}
390 other => panic!("expected LayoutMismatch, got {:?}", other.as_ref().err()),
391 }
392 std::fs::remove_file(&p).ok();
393 }
394}