1extern crate alloc;
24use alloc::collections::BTreeMap;
25use alloc::sync::Arc;
26use alloc::vec::Vec;
27use portable_atomic::{AtomicI64, AtomicU64, AtomicUsize, Ordering};
31
32use crate::wire_types::SequenceNumber;
33
34#[cfg(feature = "inspect")]
35use alloc::borrow::ToOwned;
36
37#[cfg(feature = "inspect")]
38fn dispatch_rtps_tap(label: &str, sn: SequenceNumber, payload: Vec<u8>) {
39 let ts_ns = std::time::SystemTime::now()
40 .duration_since(std::time::UNIX_EPOCH)
41 .map(|d| u64::try_from(d.as_nanos()).unwrap_or(u64::MAX))
42 .unwrap_or(0);
43 #[allow(clippy::cast_sign_loss)]
44 let corr = sn.0 as u64;
45 let frame = zerodds_inspect_endpoint::Frame::rtps(label.to_owned(), ts_ns, corr, payload);
46 zerodds_inspect_endpoint::tap::dispatch(&frame);
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum ChangeKind {
52 Alive,
54 AliveFiltered,
59 NotAliveDisposed,
61 NotAliveUnregistered,
63 NotAliveDisposedUnregistered,
65}
66
67impl ChangeKind {
68 #[must_use]
72 pub fn is_relevant(self) -> bool {
73 !matches!(self, Self::AliveFiltered)
74 }
75
76 #[must_use]
78 pub fn is_alive_kind(self) -> bool {
79 matches!(self, Self::Alive | Self::AliveFiltered)
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct CacheChange {
92 pub sequence_number: SequenceNumber,
94 pub payload: Arc<[u8]>,
96 pub kind: ChangeKind,
98 pub key_hash: Option<[u8; 16]>,
104 pub source_timestamp: Option<crate::header_extension::HeTimestamp>,
111}
112
113impl CacheChange {
114 #[must_use]
118 pub fn alive(sn: SequenceNumber, payload: Vec<u8>) -> Self {
119 Self::alive_arc(sn, Arc::from(payload))
120 }
121
122 #[must_use]
131 pub(crate) fn alive_arc(sn: SequenceNumber, payload: Arc<[u8]>) -> Self {
132 Self {
133 sequence_number: sn,
134 payload,
135 kind: ChangeKind::Alive,
136 key_hash: None,
137 source_timestamp: None,
138 }
139 }
140
141 #[must_use]
144 pub fn with_source_timestamp(
145 mut self,
146 ts: Option<crate::header_extension::HeTimestamp>,
147 ) -> Self {
148 self.source_timestamp = ts;
149 self
150 }
151
152 #[must_use]
156 pub fn lifecycle(sn: SequenceNumber, payload: Vec<u8>, kind: ChangeKind) -> Self {
157 Self {
158 sequence_number: sn,
159 payload: Arc::from(payload),
160 kind,
161 key_hash: None,
162 source_timestamp: None,
163 }
164 }
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum HistoryKind {
170 KeepAll,
175 KeepLast {
179 depth: usize,
181 },
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186#[non_exhaustive]
187pub enum CacheError {
188 CapacityExceeded,
190 DuplicateSequenceNumber,
192 ZeroDepth,
195}
196
197const STATS_SENTINEL_NO_SN: i64 = i64::MIN;
201
202#[derive(Debug)]
224pub struct HistoryCacheStats {
225 pub len: AtomicUsize,
227 pub evicted: AtomicU64,
229 pub max_sn: AtomicI64,
231 pub min_sn: AtomicI64,
233}
234
235impl Default for HistoryCacheStats {
236 fn default() -> Self {
237 Self {
238 len: AtomicUsize::new(0),
239 evicted: AtomicU64::new(0),
240 max_sn: AtomicI64::new(STATS_SENTINEL_NO_SN),
241 min_sn: AtomicI64::new(STATS_SENTINEL_NO_SN),
242 }
243 }
244}
245
246impl HistoryCacheStats {
247 #[must_use]
252 pub fn snapshot(&self) -> HistoryCacheSnapshot {
253 HistoryCacheSnapshot {
254 len: self.len.load(Ordering::Acquire),
255 evicted: self.evicted.load(Ordering::Acquire),
256 max_sn: decode_sn_atom(self.max_sn.load(Ordering::Acquire)),
257 min_sn: decode_sn_atom(self.min_sn.load(Ordering::Acquire)),
258 }
259 }
260}
261
262impl Clone for HistoryCacheStats {
263 fn clone(&self) -> Self {
264 Self {
265 len: AtomicUsize::new(self.len.load(Ordering::Acquire)),
266 evicted: AtomicU64::new(self.evicted.load(Ordering::Acquire)),
267 max_sn: AtomicI64::new(self.max_sn.load(Ordering::Acquire)),
268 min_sn: AtomicI64::new(self.min_sn.load(Ordering::Acquire)),
269 }
270 }
271}
272
273fn decode_sn_atom(v: i64) -> Option<SequenceNumber> {
274 if v == STATS_SENTINEL_NO_SN {
275 None
276 } else {
277 Some(SequenceNumber(v))
278 }
279}
280
281fn encode_sn_atom(sn: Option<SequenceNumber>) -> i64 {
282 sn.map_or(STATS_SENTINEL_NO_SN, |s| s.0)
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub struct HistoryCacheSnapshot {
290 pub len: usize,
292 pub evicted: u64,
294 pub max_sn: Option<SequenceNumber>,
296 pub min_sn: Option<SequenceNumber>,
298}
299
300#[derive(Debug)]
310pub struct HistoryCache {
311 changes: BTreeMap<SequenceNumber, CacheChange>,
312 kind: HistoryKind,
313 max_samples: usize,
314 evicted_count: u64,
315 stats: Arc<HistoryCacheStats>,
316 #[cfg(feature = "inspect")]
319 inspect_label: Option<alloc::string::String>,
320}
321
322impl Clone for HistoryCache {
323 fn clone(&self) -> Self {
324 Self {
330 changes: self.changes.clone(),
331 kind: self.kind,
332 max_samples: self.max_samples,
333 evicted_count: self.evicted_count,
334 stats: Arc::new((*self.stats).clone()),
335 #[cfg(feature = "inspect")]
336 inspect_label: self.inspect_label.clone(),
337 }
338 }
339}
340
341impl HistoryCache {
342 #[must_use]
346 pub fn new_with_kind(kind: HistoryKind, max_samples: usize) -> Self {
347 Self {
348 changes: BTreeMap::new(),
349 kind,
350 max_samples,
351 evicted_count: 0,
352 stats: Arc::new(HistoryCacheStats::default()),
353 #[cfg(feature = "inspect")]
354 inspect_label: None,
355 }
356 }
357
358 #[cfg(feature = "inspect")]
361 pub fn set_inspect_label(&mut self, label: alloc::string::String) {
362 self.inspect_label = Some(label);
363 }
364
365 #[must_use]
372 pub fn stats(&self) -> Arc<HistoryCacheStats> {
373 Arc::clone(&self.stats)
374 }
375
376 fn refresh_stats(&self) {
379 self.stats.len.store(self.changes.len(), Ordering::Release);
380 self.stats
381 .evicted
382 .store(self.evicted_count, Ordering::Release);
383 let max = self.changes.keys().next_back().copied();
384 let min = self.changes.keys().next().copied();
385 self.stats
386 .max_sn
387 .store(encode_sn_atom(max), Ordering::Release);
388 self.stats
389 .min_sn
390 .store(encode_sn_atom(min), Ordering::Release);
391 }
392
393 #[must_use]
396 pub fn new(max_samples: usize) -> Self {
397 Self::new_with_kind(HistoryKind::KeepAll, max_samples)
398 }
399
400 #[must_use]
402 pub fn kind(&self) -> HistoryKind {
403 self.kind
404 }
405
406 pub fn set_kind_and_max(&mut self, kind: HistoryKind, max_samples: usize) {
418 self.kind = kind;
419 self.max_samples = max_samples;
420 }
421
422 #[must_use]
424 pub fn max_samples(&self) -> usize {
425 self.max_samples
426 }
427
428 #[must_use]
431 pub fn evicted_count(&self) -> u64 {
432 self.evicted_count
433 }
434
435 pub fn insert(&mut self, change: CacheChange) -> Result<(), CacheError> {
442 self.insert_returning_evicted(change).map(|_| ())
446 }
447
448 pub fn insert_returning_evicted(
456 &mut self,
457 change: CacheChange,
458 ) -> Result<Option<CacheChange>, CacheError> {
459 if self.changes.contains_key(&change.sequence_number) {
460 return Err(CacheError::DuplicateSequenceNumber);
461 }
462 let cap = self.effective_max_samples()?;
463 let mut evicted: Option<CacheChange> = None;
464 if self.changes.len() >= cap {
465 match self.kind {
466 HistoryKind::KeepAll => return Err(CacheError::CapacityExceeded),
467 HistoryKind::KeepLast { .. } => {
468 if let Some((&oldest, _)) = self.changes.iter().next() {
470 evicted = self.changes.remove(&oldest);
471 self.evicted_count = self.evicted_count.saturating_add(1);
472 }
473 }
474 }
475 }
476 #[cfg(feature = "inspect")]
477 let tap_view = self.inspect_label.as_ref().map(|label| {
478 (
479 label.clone(),
480 change.sequence_number,
481 change.payload.to_vec(),
482 )
483 });
484 self.changes.insert(change.sequence_number, change);
485 self.refresh_stats();
486 #[cfg(feature = "inspect")]
487 if let Some((label, sn, payload)) = tap_view {
488 dispatch_rtps_tap(&label, sn, payload);
489 }
490 Ok(evicted)
491 }
492
493 fn effective_max_samples(&self) -> Result<usize, CacheError> {
495 match self.kind {
496 HistoryKind::KeepAll => Ok(self.max_samples),
497 HistoryKind::KeepLast { depth } => {
498 if depth == 0 {
499 return Err(CacheError::ZeroDepth);
500 }
501 Ok(core::cmp::min(depth, self.max_samples))
502 }
503 }
504 }
505
506 #[must_use]
508 pub fn get(&self, sn: SequenceNumber) -> Option<&CacheChange> {
509 self.changes.get(&sn)
510 }
511
512 pub fn remove_up_to(&mut self, sn: SequenceNumber) -> usize {
515 let keep = self.changes.split_off(&SequenceNumber(sn.0 + 1));
516 let removed = self.changes.len();
517 self.changes = keep;
518 self.refresh_stats();
519 removed
520 }
521
522 pub fn iter_range(
525 &self,
526 lo: SequenceNumber,
527 hi: SequenceNumber,
528 ) -> impl Iterator<Item = &CacheChange> + '_ {
529 self.changes.range(lo..=hi).map(|(_, v)| v)
530 }
531
532 #[must_use]
534 pub fn min_sn(&self) -> Option<SequenceNumber> {
535 self.changes.keys().next().copied()
536 }
537
538 #[must_use]
540 pub fn max_sn(&self) -> Option<SequenceNumber> {
541 self.changes.keys().next_back().copied()
542 }
543
544 #[must_use]
546 pub fn len(&self) -> usize {
547 self.changes.len()
548 }
549
550 #[must_use]
552 pub fn is_empty(&self) -> bool {
553 self.changes.is_empty()
554 }
555
556 #[must_use]
558 pub fn capacity(&self) -> usize {
559 self.max_samples
560 }
561}
562
563#[cfg(feature = "std")]
602#[derive(Debug)]
603pub struct LockFreeReadHistoryCache {
604 inner: zerodds_foundation::rcu::RcuCell<LockFreeInner>,
605 stats: Arc<HistoryCacheStats>,
606}
607
608#[cfg(feature = "std")]
612#[derive(Debug, Clone)]
613pub struct LockFreeInner {
614 pub changes: BTreeMap<SequenceNumber, CacheChange>,
616 pub kind: HistoryKind,
618 pub max_samples: usize,
620 pub evicted_count: u64,
622}
623
624#[cfg(feature = "std")]
625impl LockFreeInner {
626 fn effective_max_samples(&self) -> Result<usize, CacheError> {
627 match self.kind {
628 HistoryKind::KeepAll => Ok(self.max_samples),
629 HistoryKind::KeepLast { depth } => {
630 if depth == 0 {
631 return Err(CacheError::ZeroDepth);
632 }
633 Ok(core::cmp::min(depth, self.max_samples))
634 }
635 }
636 }
637}
638
639#[cfg(feature = "std")]
640impl LockFreeReadHistoryCache {
641 #[must_use]
643 pub fn new_with_kind(kind: HistoryKind, max_samples: usize) -> Self {
644 Self {
645 inner: zerodds_foundation::rcu::RcuCell::new(LockFreeInner {
646 changes: BTreeMap::new(),
647 kind,
648 max_samples,
649 evicted_count: 0,
650 }),
651 stats: Arc::new(HistoryCacheStats::default()),
652 }
653 }
654
655 #[must_use]
657 pub fn new(max_samples: usize) -> Self {
658 Self::new_with_kind(HistoryKind::KeepAll, max_samples)
659 }
660
661 #[must_use]
663 pub fn stats(&self) -> Arc<HistoryCacheStats> {
664 Arc::clone(&self.stats)
665 }
666
667 #[must_use]
670 pub fn snapshot(&self) -> Arc<LockFreeInner> {
671 self.inner.read()
672 }
673
674 #[must_use]
676 pub fn kind(&self) -> HistoryKind {
677 self.inner.read().kind
678 }
679
680 #[must_use]
682 pub fn evicted_count(&self) -> u64 {
683 self.stats.evicted.load(Ordering::Acquire)
684 }
685
686 #[must_use]
688 pub fn len(&self) -> usize {
689 self.stats.len.load(Ordering::Acquire)
690 }
691
692 #[must_use]
694 pub fn is_empty(&self) -> bool {
695 self.len() == 0
696 }
697
698 #[must_use]
700 pub fn min_sn(&self) -> Option<SequenceNumber> {
701 decode_sn_atom(self.stats.min_sn.load(Ordering::Acquire))
702 }
703
704 #[must_use]
706 pub fn max_sn(&self) -> Option<SequenceNumber> {
707 decode_sn_atom(self.stats.max_sn.load(Ordering::Acquire))
708 }
709
710 #[must_use]
712 pub fn capacity(&self) -> usize {
713 self.inner.read().max_samples
714 }
715
716 #[must_use]
719 pub fn get(&self, sn: SequenceNumber) -> Option<CacheChange> {
720 self.inner.read().changes.get(&sn).cloned()
721 }
722
723 #[must_use]
727 pub fn iter_range_snapshot(&self, lo: SequenceNumber, hi: SequenceNumber) -> Vec<CacheChange> {
728 let snap = self.inner.read();
729 snap.changes
730 .range(lo..=hi)
731 .map(|(_, v)| v.clone())
732 .collect()
733 }
734
735 pub fn insert(&self, change: CacheChange) -> Result<(), CacheError> {
740 let dup_or_full: Result<(), CacheError> = {
743 let snap = self.inner.read();
744 if snap.changes.contains_key(&change.sequence_number) {
745 Err(CacheError::DuplicateSequenceNumber)
746 } else {
747 let cap = snap.effective_max_samples()?;
748 if snap.changes.len() >= cap {
749 if matches!(snap.kind, HistoryKind::KeepAll) {
750 Err(CacheError::CapacityExceeded)
751 } else {
752 Ok(()) }
754 } else {
755 Ok(())
756 }
757 }
758 };
759 dup_or_full?;
760 self.inner.modify(|inner| {
761 let cap = match inner.effective_max_samples() {
763 Ok(c) => c,
764 Err(_) => return,
765 };
766 if inner.changes.len() >= cap {
767 if let HistoryKind::KeepLast { .. } = inner.kind {
768 if let Some((&oldest, _)) = inner.changes.iter().next() {
769 inner.changes.remove(&oldest);
770 inner.evicted_count = inner.evicted_count.saturating_add(1);
771 }
772 }
773 }
774 inner.changes.insert(change.sequence_number, change.clone());
775 });
776 self.refresh_stats();
777 Ok(())
778 }
779
780 pub fn remove_up_to(&self, sn: SequenceNumber) -> usize {
782 let mut removed = 0;
783 self.inner.modify(|inner| {
784 let keep = inner.changes.split_off(&SequenceNumber(sn.0 + 1));
785 removed = inner.changes.len();
786 inner.changes = keep;
787 });
788 self.refresh_stats();
789 removed
790 }
791
792 fn refresh_stats(&self) {
793 let snap = self.inner.read();
794 self.stats.len.store(snap.changes.len(), Ordering::Release);
795 self.stats
796 .evicted
797 .store(snap.evicted_count, Ordering::Release);
798 let max = snap.changes.keys().next_back().copied();
799 let min = snap.changes.keys().next().copied();
800 self.stats
801 .max_sn
802 .store(encode_sn_atom(max), Ordering::Release);
803 self.stats
804 .min_sn
805 .store(encode_sn_atom(min), Ordering::Release);
806 }
807}
808
809#[cfg(test)]
810#[allow(clippy::expect_used, clippy::unwrap_used)]
811mod tests {
812 use super::*;
813
814 fn sn(n: i64) -> SequenceNumber {
815 SequenceNumber(n)
816 }
817
818 fn alive(n: i64) -> CacheChange {
819 CacheChange::alive(sn(n), alloc::vec![n as u8])
820 }
821
822 #[test]
823 fn new_cache_is_empty() {
824 let c = HistoryCache::new(10);
825 assert_eq!(c.len(), 0);
826 assert!(c.is_empty());
827 assert_eq!(c.min_sn(), None);
828 assert_eq!(c.max_sn(), None);
829 }
830
831 #[test]
832 fn insert_and_get() {
833 let mut c = HistoryCache::new(10);
834 c.insert(alive(1)).expect("insert");
835 c.insert(alive(2)).expect("insert");
836 assert_eq!(
837 c.get(sn(1)).map(|ch| ch.payload.as_ref().to_vec()),
838 Some(alloc::vec![1])
839 );
840 assert_eq!(c.get(sn(3)), None);
841 assert_eq!(c.len(), 2);
842 }
843
844 #[test]
845 fn insert_duplicate_is_err() {
846 let mut c = HistoryCache::new(10);
847 c.insert(alive(1)).expect("insert");
848 assert_eq!(c.insert(alive(1)), Err(CacheError::DuplicateSequenceNumber));
849 }
850
851 #[test]
852 fn insert_at_capacity_is_err() {
853 let mut c = HistoryCache::new(2);
854 c.insert(alive(1)).expect("insert");
855 c.insert(alive(2)).expect("insert");
856 assert_eq!(c.insert(alive(3)), Err(CacheError::CapacityExceeded));
857 }
858
859 #[test]
860 fn min_max_sn_reflect_content() {
861 let mut c = HistoryCache::new(10);
862 c.insert(alive(5)).unwrap();
863 c.insert(alive(3)).unwrap();
864 c.insert(alive(7)).unwrap();
865 assert_eq!(c.min_sn(), Some(sn(3)));
866 assert_eq!(c.max_sn(), Some(sn(7)));
867 }
868
869 #[test]
870 fn remove_up_to_inclusive() {
871 let mut c = HistoryCache::new(10);
872 for i in 1..=5 {
873 c.insert(alive(i)).unwrap();
874 }
875 let removed = c.remove_up_to(sn(3));
876 assert_eq!(removed, 3);
877 assert_eq!(c.len(), 2);
878 assert_eq!(c.min_sn(), Some(sn(4)));
879 }
880
881 #[test]
882 fn remove_up_to_with_no_matches_is_noop() {
883 let mut c = HistoryCache::new(10);
884 c.insert(alive(10)).unwrap();
885 assert_eq!(c.remove_up_to(sn(5)), 0);
886 assert_eq!(c.len(), 1);
887 }
888
889 #[test]
890 fn iter_range_is_ordered() {
891 let mut c = HistoryCache::new(10);
892 for i in [5, 1, 3, 8, 2] {
893 c.insert(alive(i)).unwrap();
894 }
895 let collected: alloc::vec::Vec<i64> = c
896 .iter_range(sn(2), sn(5))
897 .map(|ch| ch.sequence_number.0)
898 .collect();
899 assert_eq!(collected, alloc::vec![2, 3, 5]);
900 }
901
902 #[test]
903 fn iter_range_empty_when_no_overlap() {
904 let mut c = HistoryCache::new(10);
905 c.insert(alive(1)).unwrap();
906 c.insert(alive(2)).unwrap();
907 assert_eq!(c.iter_range(sn(10), sn(20)).count(), 0);
908 }
909
910 #[test]
911 fn capacity_accessor() {
912 let c = HistoryCache::new(42);
913 assert_eq!(c.capacity(), 42);
914 }
915
916 #[test]
917 fn cache_change_alive_constructor() {
918 let ch = CacheChange::alive(sn(1), alloc::vec![1, 2, 3]);
919 assert_eq!(ch.kind, ChangeKind::Alive);
920 assert_eq!(ch.sequence_number, sn(1));
921 assert_eq!(ch.payload.as_ref(), &[1, 2, 3][..]);
922 }
923
924 #[test]
927 fn change_kind_alive_is_relevant_and_alive() {
928 assert!(ChangeKind::Alive.is_relevant());
929 assert!(ChangeKind::Alive.is_alive_kind());
930 }
931
932 #[test]
933 fn change_kind_alive_filtered_is_alive_but_not_relevant() {
934 assert!(ChangeKind::AliveFiltered.is_alive_kind());
936 assert!(!ChangeKind::AliveFiltered.is_relevant());
937 }
938
939 #[test]
940 fn change_kind_not_alive_kinds_are_not_alive() {
941 for k in [
942 ChangeKind::NotAliveDisposed,
943 ChangeKind::NotAliveUnregistered,
944 ChangeKind::NotAliveDisposedUnregistered,
945 ] {
946 assert!(!k.is_alive_kind(), "{k:?}");
947 assert!(k.is_relevant(), "{k:?}");
948 }
949 }
950
951 #[test]
952 fn change_kind_distinct_variants() {
953 let v = [
955 ChangeKind::Alive,
956 ChangeKind::AliveFiltered,
957 ChangeKind::NotAliveDisposed,
958 ChangeKind::NotAliveUnregistered,
959 ChangeKind::NotAliveDisposedUnregistered,
960 ];
961 for (i, a) in v.iter().enumerate() {
962 for (j, b) in v.iter().enumerate() {
963 if i == j {
964 assert_eq!(a, b);
965 } else {
966 assert_ne!(a, b);
967 }
968 }
969 }
970 }
971
972 #[test]
977 fn stats_default_is_empty_with_no_sn() {
978 let c = HistoryCache::new(10);
979 let snap = c.stats().snapshot();
980 assert_eq!(snap.len, 0);
981 assert_eq!(snap.evicted, 0);
982 assert_eq!(snap.max_sn, None);
983 assert_eq!(snap.min_sn, None);
984 }
985
986 #[test]
987 fn stats_track_insert_and_remove() {
988 let mut c = HistoryCache::new(10);
989 c.insert(alive(3)).unwrap();
990 c.insert(alive(5)).unwrap();
991 c.insert(alive(7)).unwrap();
992 let snap = c.stats().snapshot();
993 assert_eq!(snap.len, 3);
994 assert_eq!(snap.min_sn, Some(sn(3)));
995 assert_eq!(snap.max_sn, Some(sn(7)));
996 assert_eq!(snap.evicted, 0);
997
998 c.remove_up_to(sn(5));
999 let snap = c.stats().snapshot();
1000 assert_eq!(snap.len, 1);
1001 assert_eq!(snap.min_sn, Some(sn(7)));
1002 assert_eq!(snap.max_sn, Some(sn(7)));
1003 }
1004
1005 #[test]
1006 fn stats_track_keeplast_eviction() {
1007 let mut c = HistoryCache::new_with_kind(HistoryKind::KeepLast { depth: 2 }, 100);
1008 c.insert(alive(1)).unwrap();
1009 c.insert(alive(2)).unwrap();
1010 c.insert(alive(3)).unwrap(); let snap = c.stats().snapshot();
1012 assert_eq!(snap.len, 2);
1013 assert_eq!(snap.evicted, 1);
1014 assert_eq!(snap.min_sn, Some(sn(2)));
1015 assert_eq!(snap.max_sn, Some(sn(3)));
1016 }
1017
1018 #[test]
1019 fn stats_arc_is_shared_across_clones_of_handle() {
1020 let mut c = HistoryCache::new(10);
1024 let s1 = c.stats();
1025 let s2 = c.stats();
1026 assert!(Arc::ptr_eq(&s1, &s2));
1027 c.insert(alive(1)).unwrap();
1028 assert_eq!(s1.snapshot().len, 1);
1029 assert_eq!(s2.snapshot().len, 1);
1030 }
1031
1032 #[test]
1033 fn stats_reader_thread_sees_inserts_concurrently() {
1034 use std::sync::Arc as StdArc;
1037 use std::sync::Mutex as StdMutex;
1038 use std::thread;
1039 use std::time::Duration;
1040
1041 let cache = StdArc::new(StdMutex::new(HistoryCache::new(2_000)));
1042 let stats = cache.lock().expect("init lock").stats();
1043
1044 let writer_cache = StdArc::clone(&cache);
1045 let writer = thread::spawn(move || {
1046 for i in 1..=1_000 {
1047 let mut c = writer_cache.lock().expect("write lock");
1048 c.insert(alive(i)).expect("insert");
1049 }
1050 });
1051
1052 let reader_stats = StdArc::clone(&stats);
1053 let reader = thread::spawn(move || {
1054 for _ in 0..100 {
1056 let snap = reader_stats.snapshot();
1057 assert!(snap.len <= 1_000);
1059 if let Some(max) = snap.max_sn {
1060 assert!(max.0 >= 1 && max.0 <= 1_000);
1061 }
1062 thread::sleep(Duration::from_micros(50));
1063 }
1064 });
1065
1066 writer.join().expect("writer joined");
1067 reader.join().expect("reader joined");
1068
1069 let final_snap = stats.snapshot();
1070 assert_eq!(final_snap.len, 1_000);
1071 assert_eq!(final_snap.max_sn, Some(sn(1_000)));
1072 assert_eq!(final_snap.min_sn, Some(sn(1)));
1073 }
1074
1075 #[test]
1076 fn clone_creates_independent_stats_handles() {
1077 let mut a = HistoryCache::new(10);
1080 a.insert(alive(1)).unwrap();
1081 let b = a.clone();
1082 assert!(!Arc::ptr_eq(&a.stats(), &b.stats()));
1083 assert_eq!(a.stats().snapshot().len, 1);
1084 assert_eq!(b.stats().snapshot().len, 1);
1085
1086 let mut a_mut = a;
1087 a_mut.insert(alive(2)).unwrap();
1088 assert_eq!(a_mut.stats().snapshot().len, 2);
1089 assert_eq!(b.stats().snapshot().len, 1, "clone unaffected");
1090 }
1091
1092 #[cfg(feature = "std")]
1097 mod lock_free_tests {
1098 use super::*;
1099
1100 #[test]
1101 fn lock_free_new_is_empty() {
1102 let c = LockFreeReadHistoryCache::new(10);
1103 assert_eq!(c.len(), 0);
1104 assert!(c.is_empty());
1105 assert_eq!(c.min_sn(), None);
1106 assert_eq!(c.max_sn(), None);
1107 }
1108
1109 #[test]
1110 fn lock_free_insert_and_get() {
1111 let c = LockFreeReadHistoryCache::new(10);
1112 c.insert(alive(1)).unwrap();
1114 c.insert(alive(2)).unwrap();
1115 assert_eq!(
1116 c.get(sn(1)).map(|ch| ch.payload.as_ref().to_vec()),
1117 Some(alloc::vec![1])
1118 );
1119 assert_eq!(c.get(sn(3)), None);
1120 assert_eq!(c.len(), 2);
1121 }
1122
1123 #[test]
1124 fn lock_free_min_max_lock_free_loads() {
1125 let c = LockFreeReadHistoryCache::new(10);
1126 c.insert(alive(5)).unwrap();
1127 c.insert(alive(3)).unwrap();
1128 c.insert(alive(7)).unwrap();
1129 assert_eq!(c.min_sn(), Some(sn(3)));
1130 assert_eq!(c.max_sn(), Some(sn(7)));
1131 }
1132
1133 #[test]
1134 fn lock_free_keeplast_evicts_oldest() {
1135 let c =
1136 LockFreeReadHistoryCache::new_with_kind(HistoryKind::KeepLast { depth: 2 }, 100);
1137 c.insert(alive(1)).unwrap();
1138 c.insert(alive(2)).unwrap();
1139 c.insert(alive(3)).unwrap(); assert_eq!(c.len(), 2);
1141 assert_eq!(c.min_sn(), Some(sn(2)));
1142 assert_eq!(c.max_sn(), Some(sn(3)));
1143 assert_eq!(c.evicted_count(), 1);
1144 }
1145
1146 #[test]
1147 fn lock_free_keepall_full_rejects() {
1148 let c = LockFreeReadHistoryCache::new(2);
1149 c.insert(alive(1)).unwrap();
1150 c.insert(alive(2)).unwrap();
1151 assert_eq!(c.insert(alive(3)), Err(CacheError::CapacityExceeded));
1152 }
1153
1154 #[test]
1155 fn lock_free_duplicate_sn_rejected() {
1156 let c = LockFreeReadHistoryCache::new(10);
1157 c.insert(alive(1)).unwrap();
1158 assert_eq!(c.insert(alive(1)), Err(CacheError::DuplicateSequenceNumber));
1159 }
1160
1161 #[test]
1162 fn lock_free_remove_up_to() {
1163 let c = LockFreeReadHistoryCache::new(10);
1164 for i in 1..=5 {
1165 c.insert(alive(i)).unwrap();
1166 }
1167 let removed = c.remove_up_to(sn(3));
1168 assert_eq!(removed, 3);
1169 assert_eq!(c.len(), 2);
1170 assert_eq!(c.min_sn(), Some(sn(4)));
1171 }
1172
1173 #[test]
1174 fn lock_free_iter_range_snapshot() {
1175 let c = LockFreeReadHistoryCache::new(10);
1176 for i in 1..=5 {
1177 c.insert(alive(i)).unwrap();
1178 }
1179 let mid: alloc::vec::Vec<_> = c
1180 .iter_range_snapshot(sn(2), sn(4))
1181 .iter()
1182 .map(|ch| ch.sequence_number)
1183 .collect();
1184 assert_eq!(mid, alloc::vec![sn(2), sn(3), sn(4)]);
1185 }
1186
1187 #[test]
1188 fn lock_free_snapshot_outlives_writes() {
1189 let c = LockFreeReadHistoryCache::new(10);
1192 c.insert(alive(1)).unwrap();
1193 let snap = c.snapshot();
1194 assert_eq!(snap.changes.len(), 1);
1195
1196 c.insert(alive(2)).unwrap();
1197 c.insert(alive(3)).unwrap();
1198 assert_eq!(snap.changes.len(), 1);
1200 assert!(snap.changes.contains_key(&sn(1)));
1201 assert_eq!(c.len(), 3);
1203 }
1204
1205 #[test]
1206 fn lock_free_concurrent_readers_writers_smoke() {
1207 use std::sync::Arc as StdArc;
1208 use std::thread;
1209
1210 let cache: StdArc<LockFreeReadHistoryCache> =
1211 StdArc::new(LockFreeReadHistoryCache::new(2_000));
1212 let cache_w = StdArc::clone(&cache);
1213 let writer = thread::spawn(move || {
1214 for i in 1..=500 {
1215 cache_w.insert(alive(i)).expect("insert");
1216 }
1217 });
1218
1219 let cache_r = StdArc::clone(&cache);
1220 let reader = thread::spawn(move || {
1221 for _ in 0..200 {
1222 let snap = cache_r.snapshot();
1223 if let (Some(min), Some(max)) = (
1226 snap.changes.keys().next().copied(),
1227 snap.changes.keys().next_back().copied(),
1228 ) {
1229 let inferred_count = (max.0 - min.0 + 1) as usize;
1230 assert!(
1231 snap.changes.len() <= inferred_count,
1232 "snapshot inkonsistent"
1233 );
1234 }
1235 }
1236 });
1237
1238 writer.join().expect("writer joined");
1239 reader.join().expect("reader joined");
1240
1241 assert_eq!(cache.len(), 500);
1242 assert_eq!(cache.max_sn(), Some(sn(500)));
1243 }
1244 }
1245}