1use std::fs::{File, OpenOptions};
51use std::marker::PhantomData;
52use std::mem::size_of;
53use std::path::Path;
54use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
55
56use memmap2::{Mmap, MmapMut, MmapOptions};
57
58pub const VEC_MAGIC: u32 = 0x4150_5656;
59pub const VEC_PAYLOAD_BYTES: usize = 52;
60
61enum Mapping {
64 Writable(MmapMut),
65 ReadOnly(Mmap),
66}
67
68impl Mapping {
69 #[inline]
70 fn as_ptr(&self) -> *const u8 {
71 match self {
72 Mapping::Writable(m) => m.as_ptr(),
73 Mapping::ReadOnly(m) => m.as_ptr(),
74 }
75 }
76
77 #[inline]
78 fn is_writable(&self) -> bool {
79 matches!(self, Mapping::Writable(_))
80 }
81
82 fn flush(&self) -> Result<(), std::io::Error> {
85 match self {
86 Mapping::Writable(m) => m.flush(),
87 Mapping::ReadOnly(_) => Ok(()),
88 }
89 }
90
91 fn flush_async(&self) -> Result<(), std::io::Error> {
92 match self {
93 Mapping::Writable(m) => m.flush_async(),
94 Mapping::ReadOnly(_) => Ok(()),
95 }
96 }
97}
98
99#[repr(C, align(64))]
100pub struct VecHeader {
101 pub magic: u32,
102 pub slot_payload_size: u32,
103 pub capacity: u64,
104 pub len: AtomicU64,
105 _pad: [u8; 40],
106}
107
108#[repr(C, align(64))]
109pub struct VecSlot {
110 pub version: AtomicU32,
111 _pad: [u8; 4],
112 pub payload: [u8; VEC_PAYLOAD_BYTES],
113}
114
115const _: () = {
116 assert!(size_of::<VecHeader>() == 64);
117 assert!(size_of::<VecSlot>() == 64);
118};
119
120pub const fn vec_file_size(capacity: usize) -> usize {
121 size_of::<VecHeader>() + capacity * size_of::<VecSlot>()
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum VecError {
126 Full,
127 OutOfBounds,
128 LayoutMismatch,
129 PayloadTooLarge,
130 ReadOnly,
132 IoError(std::io::ErrorKind),
133}
134
135impl From<std::io::Error> for VecError {
136 fn from(e: std::io::Error) -> Self { Self::IoError(e.kind()) }
137}
138
139pub struct SharedVec<T: Copy + 'static> {
140 _file: File,
141 mmap: Mapping,
142 capacity: usize,
143 _phantom: PhantomData<T>,
144 header_sidecar: subetha_core::HandshakeHeader,
145 ring_sidecar: Box<subetha_core::ObservationRing>,
146}
147
148unsafe impl<T: Copy + Send + 'static> Send for SharedVec<T> {}
149unsafe impl<T: Copy + Sync + 'static> Sync for SharedVec<T> {}
150
151impl<T: Copy + Send + Sync + 'static> subetha_sidecar::AdaptiveInstance for SharedVec<T> {
152 fn header(&self) -> &subetha_core::HandshakeHeader { &self.header_sidecar }
153 fn ring(&self) -> &subetha_core::ObservationRing { &self.ring_sidecar }
154 fn make_policy(&self) -> Box<dyn subetha_sidecar::Policy> {
155 Box::new(subetha_sidecar::NoMigrationPolicy)
156 }
157}
158
159impl<T: Copy + 'static> SharedVec<T> {
160 pub fn create(
161 path: impl AsRef<Path>, capacity: usize,
162 ) -> Result<Self, VecError> {
163 if size_of::<T>() > VEC_PAYLOAD_BYTES {
164 return Err(VecError::PayloadTooLarge);
165 }
166 assert!(capacity >= 1);
167 let total = vec_file_size(capacity);
168 let file = OpenOptions::new()
169 .read(true).write(true).create(true).truncate(true)
170 .open(path.as_ref())?;
171 file.set_len(total as u64)?;
172 let mut mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
173 let hdr = mmap.as_mut_ptr() as *mut VecHeader;
174 unsafe {
175 std::ptr::write(hdr, VecHeader {
176 magic: VEC_MAGIC,
177 slot_payload_size: VEC_PAYLOAD_BYTES as u32,
178 capacity: capacity as u64,
179 len: AtomicU64::new(0),
180 _pad: [0; 40],
181 });
182 }
183 for i in 0..capacity {
184 let slot_ptr = unsafe {
185 mmap.as_mut_ptr()
186 .add(size_of::<VecHeader>())
187 .add(i * size_of::<VecSlot>())
188 } as *mut VecSlot;
189 unsafe {
190 std::ptr::write(slot_ptr, VecSlot {
191 version: AtomicU32::new(0),
192 _pad: [0; 4],
193 payload: [0u8; VEC_PAYLOAD_BYTES],
194 });
195 }
196 }
197 Ok(Self {
198 _file: file, mmap: Mapping::Writable(mmap), capacity, _phantom: PhantomData,
199 header_sidecar: subetha_core::HandshakeHeader::new(),
200 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
201 })
202 }
203
204 pub fn open(
205 path: impl AsRef<Path>, expected_capacity: usize,
206 ) -> Result<Self, VecError> {
207 if size_of::<T>() > VEC_PAYLOAD_BYTES {
208 return Err(VecError::PayloadTooLarge);
209 }
210 let file = OpenOptions::new().read(true).write(true).open(path.as_ref())?;
211 let total = vec_file_size(expected_capacity);
212 if file.metadata()?.len() < total as u64 {
213 return Err(VecError::LayoutMismatch);
214 }
215 let mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
216 let this = Self {
217 _file: file, mmap: Mapping::Writable(mmap), capacity: expected_capacity,
218 _phantom: PhantomData,
219 header_sidecar: subetha_core::HandshakeHeader::new(),
220 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
221 };
222 this.validate(expected_capacity)?;
223 Ok(this)
224 }
225
226 pub fn open_read_only(
234 path: impl AsRef<Path>, expected_capacity: usize,
235 ) -> Result<Self, VecError> {
236 if size_of::<T>() > VEC_PAYLOAD_BYTES {
237 return Err(VecError::PayloadTooLarge);
238 }
239 let file = OpenOptions::new().read(true).open(path.as_ref())?;
240 let total = vec_file_size(expected_capacity);
241 if file.metadata()?.len() < total as u64 {
242 return Err(VecError::LayoutMismatch);
243 }
244 let mmap = unsafe { MmapOptions::new().len(total).map(&file)? };
245 let this = Self {
246 _file: file, mmap: Mapping::ReadOnly(mmap), capacity: expected_capacity,
247 _phantom: PhantomData,
248 header_sidecar: subetha_core::HandshakeHeader::new(),
249 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
250 };
251 this.validate(expected_capacity)?;
252 Ok(this)
253 }
254
255 fn validate(&self, expected_capacity: usize) -> Result<(), VecError> {
257 let hdr = self.header();
258 if hdr.magic != VEC_MAGIC || hdr.capacity != expected_capacity as u64 {
259 return Err(VecError::LayoutMismatch);
260 }
261 Ok(())
262 }
263
264 #[inline]
266 pub fn is_writable(&self) -> bool {
267 self.mmap.is_writable()
268 }
269
270 #[inline]
271 pub fn capacity(&self) -> usize { self.capacity }
272
273 #[inline]
274 pub fn len(&self) -> usize {
275 self.header().len.load(Ordering::Acquire) as usize
276 }
277
278 #[inline]
279 pub fn is_empty(&self) -> bool { self.len() == 0 }
280
281 fn header(&self) -> &VecHeader {
282 unsafe { &*(self.mmap.as_ptr() as *const VecHeader) }
283 }
284
285 fn slot(&self, i: usize) -> &VecSlot {
286 assert!(i < self.capacity, "slot index {i} out of bounds for cap {}", self.capacity);
287 let base = unsafe { self.mmap.as_ptr().add(size_of::<VecHeader>()) };
288 unsafe { &*(base.add(i * size_of::<VecSlot>()) as *const VecSlot) }
289 }
290
291 fn write_slot(&self, i: usize, v: T) {
294 let slot = self.slot(i);
295 slot.version.fetch_add(1, Ordering::AcqRel);
297 let dst = unsafe {
299 let base = self.mmap.as_ptr().add(size_of::<VecHeader>())
300 .add(i * size_of::<VecSlot>())
301 .add(std::mem::offset_of!(VecSlot, payload));
302 base as *mut u8
303 };
304 unsafe {
305 std::ptr::copy_nonoverlapping(
306 &v as *const T as *const u8,
307 dst,
308 size_of::<T>(),
309 );
310 }
311 slot.version.fetch_add(1, Ordering::AcqRel);
313 }
314
315 fn read_slot(&self, i: usize) -> T {
318 let slot = self.slot(i);
319 loop {
320 let v1 = slot.version.load(Ordering::Acquire);
321 if v1 & 1 != 0 {
322 std::hint::spin_loop();
323 continue;
324 }
325 let mut out = std::mem::MaybeUninit::<T>::uninit();
326 let src = unsafe {
327 self.mmap.as_ptr().add(size_of::<VecHeader>())
328 .add(i * size_of::<VecSlot>())
329 .add(std::mem::offset_of!(VecSlot, payload))
330 };
331 unsafe {
332 std::ptr::copy_nonoverlapping(
333 src, out.as_mut_ptr() as *mut u8, size_of::<T>(),
334 );
335 }
336 let v2 = slot.version.load(Ordering::Acquire);
337 if v1 == v2 {
338 return unsafe { out.assume_init() };
339 }
340 }
341 }
342
343 pub fn push_back(&self, v: T) -> Result<usize, VecError> {
346 if !self.mmap.is_writable() {
347 return Err(VecError::ReadOnly);
348 }
349 let idx = self.header().len.fetch_add(1, Ordering::AcqRel) as usize;
350 if idx >= self.capacity {
351 self.header().len.fetch_sub(1, Ordering::AcqRel);
352 self.ring_sidecar
353 .push_op(crate::sidecar_ops::ordered::OP_INSERT, 1); return Err(VecError::Full);
355 }
356 self.write_slot(idx, v);
357 self.ring_sidecar
358 .push_op(crate::sidecar_ops::ordered::OP_INSERT, 0);
359 Ok(idx)
360 }
361
362 pub fn pop_back(&self) -> Option<T> {
364 if !self.mmap.is_writable() {
365 return None;
366 }
367 loop {
368 let cur = self.header().len.load(Ordering::Acquire);
369 if cur == 0 {
370 self.ring_sidecar
371 .push_op(crate::sidecar_ops::ordered::OP_POP, 2); return None;
373 }
374 let new = cur - 1;
375 if self.header().len.compare_exchange(
376 cur, new, Ordering::AcqRel, Ordering::Acquire,
377 ).is_ok() {
378 let v = self.read_slot(new as usize);
379 self.ring_sidecar
380 .push_op(crate::sidecar_ops::ordered::OP_POP, 0);
381 return Some(v);
382 }
383 }
384 }
385
386 pub fn get(&self, i: usize) -> Option<T> {
388 if i >= self.len() {
389 self.ring_sidecar
390 .push_op(crate::sidecar_ops::ordered::OP_GET, 2); return None;
392 }
393 let v = self.read_slot(i);
394 self.ring_sidecar
395 .push_op(crate::sidecar_ops::ordered::OP_GET, 0);
396 Some(v)
397 }
398
399 pub fn set(&self, i: usize, v: T) -> Result<(), VecError> {
402 if !self.mmap.is_writable() {
403 return Err(VecError::ReadOnly);
404 }
405 if i >= self.len() {
406 self.ring_sidecar
407 .push_op(crate::sidecar_ops::ordered::OP_INSERT, 1); return Err(VecError::OutOfBounds);
409 }
410 self.write_slot(i, v);
411 self.ring_sidecar
412 .push_op(crate::sidecar_ops::ordered::OP_INSERT, 0);
413 Ok(())
414 }
415
416 pub fn clear(&self) {
419 if !self.mmap.is_writable() {
420 return;
421 }
422 self.header().len.store(0, Ordering::Release);
423 self.ring_sidecar
424 .push_op(crate::sidecar_ops::ordered::OP_REMOVE, 0);
425 }
426
427 pub fn snapshot(&self) -> Vec<T> {
432 let n = self.len();
433 let mut out = Vec::with_capacity(n);
434 for i in 0..n {
435 out.push(self.read_slot(i));
436 }
437 self.ring_sidecar
438 .push_op(crate::sidecar_ops::ordered::OP_ITER, 0);
439 out
440 }
441
442 pub fn for_each<F: FnMut(usize, &T)>(&self, f: F) {
455 self.for_each_range(0, self.len(), f);
456 }
457
458 pub fn for_each_range<F: FnMut(usize, &T)>(&self, start: usize, end: usize, mut f: F) {
462 let end = end.min(self.len());
463 for i in start..end {
464 let v = self.read_slot(i);
465 f(i, &v);
466 }
467 self.ring_sidecar
468 .push_op(crate::sidecar_ops::ordered::OP_ITER, 0);
469 }
470
471 pub fn flush(&self) -> Result<(), VecError> {
472 self.mmap.flush()?;
473 Ok(())
474 }
475
476 pub fn flush_async(&self) -> Result<(), VecError> {
480 self.mmap.flush_async()?;
481 Ok(())
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488 use std::sync::Arc;
489 use std::thread;
490
491 #[test]
492 fn for_each_visits_every_live_slot_in_order() {
493 let p = tmp("foreach");
494 let v = SharedVec::<u64>::create(&p, 256).unwrap();
495 for i in 0..100u64 {
496 v.push_back(i * 3).unwrap();
497 }
498 let mut seen = Vec::new();
499 v.for_each(|i, x| seen.push((i, *x)));
500 assert_eq!(seen.len(), 100, "capacity beyond len is not visited");
501 assert!(seen.iter().enumerate().all(|(n, (i, x))| *i == n && *x == n as u64 * 3));
502 std::fs::remove_file(&p).ok();
503 }
504
505 #[test]
506 fn for_each_range_is_clamped_and_disjoint() {
507 let p = tmp("foreach-range");
508 let v = SharedVec::<u64>::create(&p, 64).unwrap();
509 for i in 0..40u64 {
510 v.push_back(i).unwrap();
511 }
512 let mut first = Vec::new();
513 let mut second = Vec::new();
514 v.for_each_range(0, 20, |_, x| first.push(*x));
515 v.for_each_range(20, 40, |_, x| second.push(*x));
516 first.extend_from_slice(&second);
518 assert_eq!(first, (0..40u64).collect::<Vec<_>>());
519 let mut over = Vec::new();
521 v.for_each_range(30, 9999, |_, x| over.push(*x));
522 assert_eq!(over.len(), 10);
523 std::fs::remove_file(&p).ok();
524 }
525
526 #[test]
527 fn for_each_agrees_with_snapshot() {
528 let p = tmp("foreach-vs-snapshot");
529 let v = SharedVec::<u64>::create(&p, 512).unwrap();
530 for i in 0..300u64 {
531 v.push_back(i ^ 0xa5a5).unwrap();
532 }
533 let mut walked = Vec::new();
534 v.for_each(|_, x| walked.push(*x));
535 assert_eq!(walked, v.snapshot());
536 std::fs::remove_file(&p).ok();
537 }
538
539 #[test]
540 fn a_read_only_vec_reads_the_same_and_refuses_writes() {
541 let p = tmp("readonly");
542 {
543 let w = SharedVec::<u64>::create(&p, 64).unwrap();
544 for i in 0..10u64 {
545 w.push_back(i * 7).unwrap();
546 }
547 w.flush().unwrap();
548 }
549 let r = SharedVec::<u64>::open_read_only(&p, 64).unwrap();
550 assert!(!r.is_writable());
551 assert_eq!(r.len(), 10);
552 assert_eq!(r.get(3), Some(21));
553 assert_eq!(r.snapshot(), (0..10u64).map(|i| i * 7).collect::<Vec<_>>());
554 assert_eq!(r.push_back(1), Err(VecError::ReadOnly));
555 assert_eq!(r.set(0, 1), Err(VecError::ReadOnly));
556 assert_eq!(r.pop_back(), None);
557 r.clear();
558 assert_eq!(r.len(), 10, "clear on a read-only vec changes nothing");
559 r.flush().unwrap();
560 std::fs::remove_file(&p).ok();
561 }
562
563 #[test]
564 fn a_read_only_open_still_validates_the_header() {
565 let p = tmp("readonly-mismatch");
566 {
567 let w = SharedVec::<u64>::create(&p, 64).unwrap();
568 w.push_back(1).unwrap();
569 w.flush().unwrap();
570 }
571 assert_eq!(
572 SharedVec::<u64>::open_read_only(&p, 32).err(),
573 Some(VecError::LayoutMismatch)
574 );
575 std::fs::remove_file(&p).ok();
576 }
577
578 fn tmp(name: &str) -> std::path::PathBuf {
579 let mut p = std::env::temp_dir();
580 let pid = std::process::id();
581 p.push(format!("subetha-vec-{name}-{pid}.bin"));
582 p
583 }
584
585 #[test]
586 fn create_initial_state_is_empty() {
587 let p = tmp("init");
588 let v: SharedVec<u32> = SharedVec::create(&p, 16).unwrap();
589 assert_eq!(v.capacity(), 16);
590 assert_eq!(v.len(), 0);
591 assert!(v.is_empty());
592 assert_eq!(v.get(0), None);
593 std::fs::remove_file(&p).ok();
594 }
595
596 #[test]
597 fn push_back_advances_len_and_get_round_trip() {
598 let p = tmp("push");
599 let v: SharedVec<u32> = SharedVec::create(&p, 8).unwrap();
600 for i in 0..5u32 {
601 let idx = v.push_back(i * 10).unwrap();
602 assert_eq!(idx, i as usize);
603 }
604 assert_eq!(v.len(), 5);
605 for i in 0..5 {
606 assert_eq!(v.get(i), Some((i as u32) * 10));
607 }
608 assert_eq!(v.get(5), None);
609 std::fs::remove_file(&p).ok();
610 }
611
612 #[test]
613 fn full_capacity_returns_error() {
614 let p = tmp("full");
615 let v: SharedVec<u32> = SharedVec::create(&p, 4).unwrap();
616 for i in 0..4u32 { v.push_back(i).unwrap(); }
617 assert_eq!(v.push_back(99).err(), Some(VecError::Full));
618 assert_eq!(v.len(), 4); std::fs::remove_file(&p).ok();
620 }
621
622 #[test]
623 fn pop_back_returns_last_then_none() {
624 let p = tmp("pop");
625 let v: SharedVec<u32> = SharedVec::create(&p, 8).unwrap();
626 v.push_back(10).unwrap();
627 v.push_back(20).unwrap();
628 v.push_back(30).unwrap();
629 assert_eq!(v.pop_back(), Some(30));
630 assert_eq!(v.pop_back(), Some(20));
631 assert_eq!(v.len(), 1);
632 assert_eq!(v.pop_back(), Some(10));
633 assert_eq!(v.pop_back(), None);
634 std::fs::remove_file(&p).ok();
635 }
636
637 #[test]
638 fn set_replaces_value_at_index() {
639 let p = tmp("set");
640 let v: SharedVec<u32> = SharedVec::create(&p, 8).unwrap();
641 v.push_back(1).unwrap();
642 v.push_back(2).unwrap();
643 v.set(0, 100).unwrap();
644 assert_eq!(v.get(0), Some(100));
645 assert_eq!(v.get(1), Some(2));
646 assert_eq!(v.set(2, 200).err(), Some(VecError::OutOfBounds));
647 std::fs::remove_file(&p).ok();
648 }
649
650 #[test]
651 fn clear_resets_len_to_zero() {
652 let p = tmp("clear");
653 let v: SharedVec<u32> = SharedVec::create(&p, 8).unwrap();
654 for i in 0..5u32 { v.push_back(i).unwrap(); }
655 assert_eq!(v.len(), 5);
656 v.clear();
657 assert_eq!(v.len(), 0);
658 assert_eq!(v.get(0), None);
659 v.push_back(42).unwrap();
661 assert_eq!(v.get(0), Some(42));
662 std::fs::remove_file(&p).ok();
663 }
664
665 #[test]
666 fn snapshot_returns_consistent_prefix() {
667 let p = tmp("snapshot");
668 let v: SharedVec<u32> = SharedVec::create(&p, 16).unwrap();
669 for i in 0..7u32 { v.push_back(i + 100).unwrap(); }
670 let snap = v.snapshot();
671 assert_eq!(snap, vec![100, 101, 102, 103, 104, 105, 106]);
672 std::fs::remove_file(&p).ok();
673 }
674
675 #[test]
676 fn cross_handle_visibility() {
677 let p = tmp("cross-handle");
678 let writer: SharedVec<u32> = SharedVec::create(&p, 8).unwrap();
679 let reader: SharedVec<u32> = SharedVec::open(&p, 8).unwrap();
680 writer.push_back(777).unwrap();
681 assert_eq!(reader.get(0), Some(777));
682 reader.push_back(888).unwrap();
683 assert_eq!(writer.get(1), Some(888));
684 assert_eq!(writer.len(), 2);
685 std::fs::remove_file(&p).ok();
686 }
687
688 #[test]
689 fn concurrent_pushers_all_land_at_distinct_indices() {
690 let p = tmp("concurrent");
691 let v: Arc<SharedVec<u32>> = Arc::new(SharedVec::create(&p, 1024).unwrap());
692 let n_threads = 4;
693 let per_thread = 50u32;
694 let mut handles = vec![];
695 for t in 0..n_threads {
696 let v = v.clone();
697 handles.push(thread::spawn(move || {
698 let mut indices = vec![];
699 for i in 0..per_thread {
700 let val = (t as u32) * per_thread + i;
701 let idx = v.push_back(val).unwrap();
702 indices.push(idx);
703 }
704 indices
705 }));
706 }
707 let mut all_indices: Vec<usize> = handles.into_iter()
708 .flat_map(|h| h.join().unwrap())
709 .collect();
710 all_indices.sort();
711 for (expected, actual) in all_indices.iter().enumerate() {
712 assert_eq!(*actual, expected,
713 "indices must form a contiguous 0..N sequence");
714 }
715 assert_eq!(v.len(), (n_threads * per_thread as usize));
716 std::fs::remove_file(&p).ok();
717 }
718
719 #[test]
720 fn payload_too_large_at_create() {
721 #[allow(dead_code)] struct Big([u8; VEC_PAYLOAD_BYTES + 1]);
723 impl Copy for Big {}
724 impl Clone for Big { fn clone(&self) -> Self { *self } }
725 let p = tmp("too-large");
726 let r = SharedVec::<Big>::create(&p, 4);
727 assert_eq!(r.err(), Some(VecError::PayloadTooLarge));
728 std::fs::remove_file(&p).ok();
729 }
730
731 #[test]
732 fn struct_payload_round_trip() {
733 #[derive(Clone, Copy, Debug, PartialEq)]
734 #[repr(C)]
735 struct Point { x: f64, y: f64, z: f64 }
736 let p = tmp("struct");
737 let v: SharedVec<Point> = SharedVec::create(&p, 8).unwrap();
738 v.push_back(Point { x: 1.0, y: 2.0, z: 3.0 }).unwrap();
739 v.push_back(Point { x: -1.5, y: 0.0, z: 7.25 }).unwrap();
740 assert_eq!(v.get(0), Some(Point { x: 1.0, y: 2.0, z: 3.0 }));
741 assert_eq!(v.get(1), Some(Point { x: -1.5, y: 0.0, z: 7.25 }));
742 std::fs::remove_file(&p).ok();
743 }
744
745 #[test]
746 fn disk_persistence_data_survives_reopen() {
747 let p = tmp("disk");
748 {
749 let v: SharedVec<u32> = SharedVec::create(&p, 8).unwrap();
750 for i in 0..4u32 { v.push_back(i * 100).unwrap(); }
751 v.flush().unwrap();
752 }
753 let v2: SharedVec<u32> = SharedVec::open(&p, 8).unwrap();
754 assert_eq!(v2.len(), 4);
755 for i in 0..4 {
756 assert_eq!(v2.get(i), Some((i as u32) * 100));
757 }
758 std::fs::remove_file(&p).ok();
759 }
760
761 #[test]
762 fn concurrent_reader_during_writes_sees_consistent_data() {
763 let p = tmp("read-during-write");
764 let v: Arc<SharedVec<u32>> = Arc::new(SharedVec::create(&p, 256).unwrap());
765 let v_w = v.clone();
766 let writer = thread::spawn(move || {
767 for i in 0..100u32 {
768 v_w.push_back(i).unwrap();
769 }
770 });
771 let v_r = v.clone();
772 let reader = thread::spawn(move || {
773 let mut last_len = 0;
774 loop {
775 let n = v_r.len();
776 if n == 100 { break; }
777 for i in last_len..n {
779 let got = v_r.get(i);
780 assert_eq!(got, Some(i as u32),
781 "slot {i} should hold {i}, got {got:?}");
782 }
783 last_len = n;
784 std::thread::yield_now();
785 }
786 });
787 writer.join().unwrap();
788 reader.join().unwrap();
789 std::fs::remove_file(&p).ok();
790 }
791}