1use std::marker::PhantomData;
33use std::path::Path;
34
35use crate::shared_region::{OffsetPtr, RegionError, SharedRegion};
36
37pub const NIL_INDEX: u32 = u32::MAX;
39
40pub const HEAD_INDEX: u32 = 0;
43
44#[derive(Debug)]
45#[repr(C)]
46pub struct Node<T: Copy + Default + 'static> {
47 pub value: T,
48 pub next: u32,
49 pub prev: u32,
50}
51
52impl<T: Copy + Default + 'static> Clone for Node<T> {
55 fn clone(&self) -> Self { *self }
56}
57impl<T: Copy + Default + 'static> Copy for Node<T> {}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61#[repr(C)]
62pub struct NodeHandle<T> {
63 pub index: u32,
64 _phantom: PhantomData<T>,
65}
66
67impl<T> NodeHandle<T> {
68 pub const NIL: Self = Self { index: NIL_INDEX, _phantom: PhantomData };
69
70 #[inline]
71 pub fn new(index: u32) -> Self {
72 Self { index, _phantom: PhantomData }
73 }
74
75 #[inline]
76 pub fn is_nil(self) -> bool { self.index == NIL_INDEX }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum LinkedListError {
81 Region(RegionError),
82 InvalidHandle,
83 LayoutMismatch,
84 IoError(std::io::ErrorKind),
85}
86
87impl From<RegionError> for LinkedListError {
88 fn from(e: RegionError) -> Self { Self::Region(e) }
89}
90impl From<std::io::Error> for LinkedListError {
91 fn from(e: std::io::Error) -> Self { Self::IoError(e.kind()) }
92}
93
94pub struct SharedLinkedList<T: Copy + Default + 'static> {
95 region: SharedRegion<Node<T>>,
96 slots_base: usize,
103 _phantom: PhantomData<T>,
104 header_sidecar: subetha_core::HandshakeHeader,
105 ring_sidecar: Box<subetha_core::ObservationRing>,
106}
107
108impl<T: Copy + Default + Send + Sync + 'static>
109 subetha_sidecar::AdaptiveInstance for SharedLinkedList<T>
110{
111 fn header(&self) -> &subetha_core::HandshakeHeader { &self.header_sidecar }
112 fn ring(&self) -> &subetha_core::ObservationRing { &self.ring_sidecar }
113 fn make_policy(&self) -> Box<dyn subetha_sidecar::Policy> {
114 Box::new(subetha_sidecar::NoMigrationPolicy)
115 }
116}
117
118impl<T: Copy + Default + 'static> SharedLinkedList<T> {
119 pub fn create(
120 path: impl AsRef<Path>, capacity: usize,
121 ) -> Result<Self, LinkedListError> {
122 assert!(capacity >= 2, "capacity must include sentinel head + at least one node");
123 let region = SharedRegion::<Node<T>>::create(path, capacity)?;
124 let head = Node {
127 value: T::default(),
128 next: HEAD_INDEX,
129 prev: HEAD_INDEX,
130 };
131 let head_ptr = region.allocate(head)?;
132 assert_eq!(head_ptr.index, HEAD_INDEX,
133 "first allocation must be slot 0");
134 let slots_base = Self::slots_base_of(®ion);
135 Ok(Self {
136 region, slots_base, _phantom: PhantomData,
137 header_sidecar: subetha_core::HandshakeHeader::new(),
138 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
139 })
140 }
141
142 pub fn open(
143 path: impl AsRef<Path>, capacity: usize,
144 ) -> Result<Self, LinkedListError> {
145 let region = SharedRegion::<Node<T>>::open(path, capacity)?;
146 let slots_base = Self::slots_base_of(®ion);
147 Ok(Self {
148 region, slots_base, _phantom: PhantomData,
149 header_sidecar: subetha_core::HandshakeHeader::new(),
150 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
151 })
152 }
153
154 #[inline]
157 fn slots_base_of(region: &SharedRegion<Node<T>>) -> usize {
158 region.mmap_ptr() as usize
159 + std::mem::size_of::<crate::shared_region::RegionHeader>()
160 + region.capacity() * std::mem::size_of::<u32>()
161 }
162
163 #[inline]
164 fn node_ptr(&self, idx: u32) -> usize {
165 self.slots_base + idx as usize * std::mem::size_of::<Node<T>>()
166 }
167
168 #[inline]
174 fn read_value(&self, idx: u32) -> T {
175 let addr = self.node_ptr(idx) + std::mem::offset_of!(Node<T>, value);
176 unsafe { (addr as *const T).read() }
177 }
178 #[inline]
179 fn read_next(&self, idx: u32) -> u32 {
180 let addr = self.node_ptr(idx) + std::mem::offset_of!(Node<T>, next);
181 unsafe { (addr as *const u32).read() }
182 }
183 #[inline]
184 fn read_prev(&self, idx: u32) -> u32 {
185 let addr = self.node_ptr(idx) + std::mem::offset_of!(Node<T>, prev);
186 unsafe { (addr as *const u32).read() }
187 }
188 #[inline]
189 fn set_next(&self, idx: u32, value: u32) {
190 let addr = self.node_ptr(idx) + std::mem::offset_of!(Node<T>, next);
191 unsafe { (addr as *mut u32).write(value); }
192 }
193 #[inline]
194 fn set_prev(&self, idx: u32, value: u32) {
195 let addr = self.node_ptr(idx) + std::mem::offset_of!(Node<T>, prev);
196 unsafe { (addr as *mut u32).write(value); }
197 }
198 #[inline]
199 fn set_value(&self, idx: u32, value: T) {
200 let addr = self.node_ptr(idx) + std::mem::offset_of!(Node<T>, value);
201 unsafe { (addr as *mut T).write(value); }
202 }
203
204 #[inline]
205 pub fn capacity(&self) -> usize { self.region.capacity() }
206
207 pub fn len(&self) -> usize {
209 self.region.len().saturating_sub(1)
210 }
211
212 pub fn is_empty(&self) -> bool { self.len() == 0 }
213
214 pub fn region(&self) -> &SharedRegion<Node<T>> { &self.region }
217
218 fn read_node(&self, idx: u32) -> Node<T> {
219 self.region.get(OffsetPtr::new(idx))
220 .expect("valid node index")
221 }
222
223 pub fn push_front(&self, value: T) -> Result<NodeHandle<T>, LinkedListError> {
225 let r = self.push_front_inner(value);
226 self.ring_sidecar.push_op(
227 crate::sidecar_ops::linked_list::OP_PUSH_FRONT,
228 if r.is_err() { 1 } else { 0 },
229 );
230 r
231 }
232
233 fn push_front_inner(&self, value: T) -> Result<NodeHandle<T>, LinkedListError> {
234 let old_first = self.read_next(HEAD_INDEX);
235 let new = Node {
236 value,
237 next: old_first,
238 prev: HEAD_INDEX,
239 };
240 let new_ptr = self.region.allocate(new)?;
241 let new_idx = new_ptr.index;
242 if old_first != HEAD_INDEX {
245 self.set_prev(old_first, new_idx);
246 } else {
247 self.set_prev(HEAD_INDEX, new_idx);
248 }
249 self.set_next(HEAD_INDEX, new_idx);
250 Ok(NodeHandle::new(new_idx))
251 }
252
253 pub fn push_back(&self, value: T) -> Result<NodeHandle<T>, LinkedListError> {
255 let r = self.push_back_inner(value);
256 self.ring_sidecar.push_op(
257 crate::sidecar_ops::linked_list::OP_PUSH_BACK,
258 if r.is_err() { 1 } else { 0 },
259 );
260 r
261 }
262
263 fn push_back_inner(&self, value: T) -> Result<NodeHandle<T>, LinkedListError> {
264 let old_last = self.read_prev(HEAD_INDEX);
265 let new = Node {
266 value,
267 next: HEAD_INDEX,
268 prev: old_last,
269 };
270 let new_ptr = self.region.allocate(new)?;
271 let new_idx = new_ptr.index;
272 if old_last != HEAD_INDEX {
275 self.set_next(old_last, new_idx);
276 } else {
277 self.set_next(HEAD_INDEX, new_idx);
278 }
279 self.set_prev(HEAD_INDEX, new_idx);
280 Ok(NodeHandle::new(new_idx))
281 }
282
283 pub fn pop_front(&self) -> Option<T> {
285 let first_idx = self.read_next(HEAD_INDEX);
286 if first_idx == HEAD_INDEX {
287 self.ring_sidecar
288 .push_op(crate::sidecar_ops::linked_list::OP_POP_FRONT, 2); return None;
290 }
291 let r = self.remove_by_index(first_idx);
292 self.ring_sidecar.push_op(
293 crate::sidecar_ops::linked_list::OP_POP_FRONT,
294 if r.is_none() { 2 } else { 0 },
295 );
296 r
297 }
298
299 pub fn pop_back(&self) -> Option<T> {
301 let head = self.read_node(HEAD_INDEX);
302 if head.prev == HEAD_INDEX {
303 self.ring_sidecar
304 .push_op(crate::sidecar_ops::linked_list::OP_POP_BACK, 2); return None;
306 }
307 let last_idx = head.prev;
308 let r = self.remove_by_index(last_idx);
309 self.ring_sidecar.push_op(
310 crate::sidecar_ops::linked_list::OP_POP_BACK,
311 if r.is_none() { 2 } else { 0 },
312 );
313 r
314 }
315
316 pub fn remove(&self, handle: NodeHandle<T>) -> Option<T> {
320 if handle.is_nil() || handle.index == HEAD_INDEX {
321 self.ring_sidecar
322 .push_op(crate::sidecar_ops::linked_list::OP_REMOVE, 2); return None;
324 }
325 let r = self.remove_by_index(handle.index);
326 self.ring_sidecar.push_op(
327 crate::sidecar_ops::linked_list::OP_REMOVE,
328 if r.is_none() { 2 } else { 0 },
329 );
330 r
331 }
332
333 fn remove_by_index(&self, idx: u32) -> Option<T> {
334 let node_prev = self.read_prev(idx);
337 let node_next = self.read_next(idx);
338 let node_value = self.read_value(idx);
339 if node_prev == HEAD_INDEX {
341 self.set_next(HEAD_INDEX, node_next);
342 if node_next == HEAD_INDEX {
343 self.set_prev(HEAD_INDEX, HEAD_INDEX);
344 }
345 } else {
346 self.set_next(node_prev, node_next);
347 }
348 if node_next == HEAD_INDEX {
350 self.set_prev(HEAD_INDEX, node_prev);
351 if node_prev == HEAD_INDEX {
352 self.set_next(HEAD_INDEX, HEAD_INDEX);
353 }
354 } else {
355 self.set_prev(node_next, node_prev);
356 }
357 self.region.free(OffsetPtr::new(idx)).ok();
358 Some(node_value)
359 }
360
361 pub fn get(&self, handle: NodeHandle<T>) -> Option<T> {
364 let r = if handle.is_nil() || handle.index == HEAD_INDEX {
365 None
366 } else {
367 Some(self.read_value(handle.index))
368 };
369 self.ring_sidecar.push_op(
370 crate::sidecar_ops::linked_list::OP_ITER,
371 if r.is_none() { 2 } else { 0 },
372 );
373 r
374 }
375
376 pub fn set(&self, handle: NodeHandle<T>, value: T) -> Result<(), LinkedListError> {
379 if handle.is_nil() || handle.index == HEAD_INDEX {
380 self.ring_sidecar
381 .push_op(crate::sidecar_ops::linked_list::OP_PUSH_BACK, 1); return Err(LinkedListError::InvalidHandle);
383 }
384 self.set_value(handle.index, value);
385 self.ring_sidecar
386 .push_op(crate::sidecar_ops::linked_list::OP_PUSH_BACK, 0);
387 Ok(())
388 }
389
390 pub fn first(&self) -> Option<T> {
392 let head = self.read_node(HEAD_INDEX);
393 let r = if head.next == HEAD_INDEX {
394 None
395 } else {
396 Some(self.read_node(head.next).value)
397 };
398 self.ring_sidecar.push_op(
399 crate::sidecar_ops::linked_list::OP_ITER,
400 if r.is_none() { 2 } else { 0 },
401 );
402 r
403 }
404
405 pub fn last(&self) -> Option<T> {
407 let head = self.read_node(HEAD_INDEX);
408 let r = if head.prev == HEAD_INDEX {
409 None
410 } else {
411 Some(self.read_node(head.prev).value)
412 };
413 self.ring_sidecar.push_op(
414 crate::sidecar_ops::linked_list::OP_ITER,
415 if r.is_none() { 2 } else { 0 },
416 );
417 r
418 }
419
420 pub fn iter_forward(&self) -> Vec<T> {
422 let mut out = Vec::with_capacity(self.len());
423 let mut cur = self.read_node(HEAD_INDEX).next;
424 while cur != HEAD_INDEX {
425 let node = self.read_node(cur);
426 out.push(node.value);
427 cur = node.next;
428 }
429 self.ring_sidecar
430 .push_op(crate::sidecar_ops::linked_list::OP_ITER, 0);
431 out
432 }
433
434 pub fn iter_backward(&self) -> Vec<T> {
436 let mut out = Vec::with_capacity(self.len());
437 let mut cur = self.read_node(HEAD_INDEX).prev;
438 while cur != HEAD_INDEX {
439 let node = self.read_node(cur);
440 out.push(node.value);
441 cur = node.prev;
442 }
443 self.ring_sidecar
444 .push_op(crate::sidecar_ops::linked_list::OP_ITER, 0);
445 out
446 }
447
448 pub fn iter_forward_with_handles(&self) -> Vec<(NodeHandle<T>, T)> {
451 let mut out = Vec::with_capacity(self.len());
452 let mut cur = self.read_node(HEAD_INDEX).next;
453 while cur != HEAD_INDEX {
454 let node = self.read_node(cur);
455 out.push((NodeHandle::new(cur), node.value));
456 cur = node.next;
457 }
458 self.ring_sidecar
459 .push_op(crate::sidecar_ops::linked_list::OP_ITER, 0);
460 out
461 }
462
463 pub fn flush(&self) -> Result<(), LinkedListError> {
464 Ok(self.region.flush()?)
465 }
466
467 pub fn flush_async(&self) -> Result<(), LinkedListError> {
468 Ok(self.region.flush_async()?)
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475
476 fn tmp(name: &str) -> std::path::PathBuf {
477 let mut p = std::env::temp_dir();
478 let pid = std::process::id();
479 p.push(format!("subetha-linkedlist-{name}-{pid}.bin"));
480 p
481 }
482
483 #[test]
484 fn create_initial_state_is_empty() {
485 let p = tmp("init");
486 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 32).unwrap();
487 assert!(l.is_empty());
488 assert_eq!(l.len(), 0);
489 assert_eq!(l.first(), None);
490 assert_eq!(l.last(), None);
491 assert_eq!(l.iter_forward(), Vec::<u32>::new());
492 std::fs::remove_file(&p).ok();
493 }
494
495 #[test]
496 fn push_back_and_iterate_forward() {
497 let p = tmp("push-back");
498 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 32).unwrap();
499 for i in [10u32, 20, 30, 40, 50] { l.push_back(i).unwrap(); }
500 assert_eq!(l.len(), 5);
501 assert_eq!(l.iter_forward(), vec![10, 20, 30, 40, 50]);
502 assert_eq!(l.iter_backward(), vec![50, 40, 30, 20, 10]);
503 assert_eq!(l.first(), Some(10));
504 assert_eq!(l.last(), Some(50));
505 std::fs::remove_file(&p).ok();
506 }
507
508 #[test]
509 fn push_front_inserts_at_head() {
510 let p = tmp("push-front");
511 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 32).unwrap();
512 for i in [10u32, 20, 30] { l.push_front(i).unwrap(); }
513 assert_eq!(l.iter_forward(), vec![30, 20, 10]);
514 std::fs::remove_file(&p).ok();
515 }
516
517 #[test]
518 fn pop_front_and_back_round_trip() {
519 let p = tmp("pop");
520 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 32).unwrap();
521 for i in [10u32, 20, 30, 40] { l.push_back(i).unwrap(); }
522 assert_eq!(l.pop_front(), Some(10));
523 assert_eq!(l.pop_back(), Some(40));
524 assert_eq!(l.iter_forward(), vec![20, 30]);
525 assert_eq!(l.pop_front(), Some(20));
526 assert_eq!(l.pop_back(), Some(30));
527 assert!(l.is_empty());
528 assert_eq!(l.pop_front(), None);
529 assert_eq!(l.pop_back(), None);
530 std::fs::remove_file(&p).ok();
531 }
532
533 #[test]
534 fn remove_by_handle_in_middle_preserves_integrity() {
535 let p = tmp("remove-middle");
536 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 32).unwrap();
537 let h1 = l.push_back(10).unwrap();
538 let h2 = l.push_back(20).unwrap();
539 let h3 = l.push_back(30).unwrap();
540 let h4 = l.push_back(40).unwrap();
541 let h5 = l.push_back(50).unwrap();
542 assert_eq!(l.remove(h3), Some(30));
544 assert_eq!(l.len(), 4);
545 assert_eq!(l.iter_forward(), vec![10, 20, 40, 50]);
546 assert_eq!(l.iter_backward(), vec![50, 40, 20, 10]);
547 assert_eq!(l.remove(h1), Some(10));
549 assert_eq!(l.iter_forward(), vec![20, 40, 50]);
550 assert_eq!(l.remove(h5), Some(50));
552 assert_eq!(l.iter_forward(), vec![20, 40]);
553 assert_eq!(l.remove(h2), Some(20));
555 assert_eq!(l.remove(h4), Some(40));
556 assert!(l.is_empty());
557 std::fs::remove_file(&p).ok();
558 }
559
560 #[test]
561 fn remove_nil_or_head_returns_none() {
562 let p = tmp("remove-nil");
563 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 8).unwrap();
564 l.push_back(1).unwrap();
565 assert_eq!(l.remove(NodeHandle::NIL), None);
566 assert_eq!(l.remove(NodeHandle::new(HEAD_INDEX)), None);
567 std::fs::remove_file(&p).ok();
568 }
569
570 #[test]
571 fn get_and_set_via_handle() {
572 let p = tmp("get-set");
573 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 8).unwrap();
574 let h = l.push_back(42).unwrap();
575 assert_eq!(l.get(h), Some(42));
576 l.set(h, 100).unwrap();
577 assert_eq!(l.get(h), Some(100));
578 assert_eq!(l.iter_forward(), vec![100]);
579 assert_eq!(l.get(NodeHandle::NIL), None);
581 assert!(l.set(NodeHandle::NIL, 0).is_err());
583 std::fs::remove_file(&p).ok();
584 }
585
586 #[test]
587 fn full_capacity_returns_error() {
588 let p = tmp("full");
589 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 4).unwrap();
591 l.push_back(1).unwrap();
592 l.push_back(2).unwrap();
593 l.push_back(3).unwrap();
594 assert!(l.push_back(4).is_err());
595 l.pop_front().unwrap();
597 l.push_back(4).unwrap();
598 assert_eq!(l.iter_forward(), vec![2, 3, 4]);
599 std::fs::remove_file(&p).ok();
600 }
601
602 #[test]
603 fn iter_forward_with_handles_returns_pairs() {
604 let p = tmp("iter-handles");
605 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 16).unwrap();
606 let h1 = l.push_back(10).unwrap();
607 let h2 = l.push_back(20).unwrap();
608 let h3 = l.push_back(30).unwrap();
609 let pairs = l.iter_forward_with_handles();
610 assert_eq!(pairs.len(), 3);
611 assert_eq!(pairs[0], (h1, 10));
612 assert_eq!(pairs[1], (h2, 20));
613 assert_eq!(pairs[2], (h3, 30));
614 std::fs::remove_file(&p).ok();
615 }
616
617 #[test]
618 fn cross_handle_visibility() {
619 let p = tmp("cross-handle");
620 let writer: SharedLinkedList<u32> = SharedLinkedList::create(&p, 16).unwrap();
621 let reader: SharedLinkedList<u32> = SharedLinkedList::open(&p, 16).unwrap();
622 writer.push_back(100).unwrap();
623 writer.push_back(200).unwrap();
624 assert_eq!(reader.iter_forward(), vec![100, 200]);
625 let h = writer.push_back(300).unwrap();
626 assert_eq!(reader.iter_forward(), vec![100, 200, 300]);
627 writer.remove(h);
628 assert_eq!(reader.iter_forward(), vec![100, 200]);
629 std::fs::remove_file(&p).ok();
630 }
631
632 #[test]
633 fn struct_payload_round_trip() {
634 #[derive(Clone, Copy, Debug, PartialEq, Default)]
635 #[repr(C)]
636 struct Event { ts_us: u64, code: u32 }
637 let p = tmp("struct");
638 let l: SharedLinkedList<Event> = SharedLinkedList::create(&p, 16).unwrap();
639 let h1 = l.push_back(Event { ts_us: 100, code: 1 }).unwrap();
640 let _h2 = l.push_back(Event { ts_us: 200, code: 2 }).unwrap();
641 assert_eq!(l.get(h1), Some(Event { ts_us: 100, code: 1 }));
642 let items = l.iter_forward();
643 assert_eq!(items.len(), 2);
644 std::fs::remove_file(&p).ok();
645 }
646
647 #[test]
648 fn disk_persistence_survives_reopen() {
649 let p = tmp("disk");
650 {
651 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 16).unwrap();
652 for i in [10u32, 20, 30, 40] { l.push_back(i).unwrap(); }
653 l.flush().unwrap();
654 }
655 let l2: SharedLinkedList<u32> = SharedLinkedList::open(&p, 16).unwrap();
656 assert_eq!(l2.iter_forward(), vec![10, 20, 30, 40]);
657 std::fs::remove_file(&p).ok();
658 }
659
660 #[test]
661 fn lru_pattern_move_to_front() {
662 let p = tmp("lru");
666 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 32).unwrap();
667 let h_a = l.push_back(1).unwrap(); let _h_b = l.push_back(2).unwrap();
669 let _h_c = l.push_back(3).unwrap(); let val_a = l.remove(h_a).unwrap();
672 l.push_front(val_a).unwrap();
673 assert_eq!(l.iter_forward(), vec![1, 2, 3]);
676 assert_eq!(l.pop_back(), Some(3)); std::fs::remove_file(&p).ok();
680 }
681
682 #[test]
683 fn free_list_pattern_uses_handles() {
684 let p = tmp("free-list");
687 let l: SharedLinkedList<u32> = SharedLinkedList::create(&p, 16).unwrap();
688 let mut handles = vec![];
689 for i in 0..10u32 { handles.push(l.push_back(i).unwrap()); }
690 for (idx, h) in handles.iter().enumerate() {
692 if idx % 2 == 0 {
693 let v = l.remove(*h).unwrap();
694 assert_eq!(v, idx as u32);
695 }
696 }
697 assert_eq!(l.len(), 5);
698 assert_eq!(l.iter_forward(), vec![1, 3, 5, 7, 9]);
699 std::fs::remove_file(&p).ok();
700 }
701}