1use std::{
19 collections::{HashMap, HashSet},
20 fmt::{Debug, Display},
21 hash::Hash,
22 sync::Arc,
23};
24
25use ahash::{AHashMap, AHashSet};
26use arc_swap::ArcSwap;
27use ustr::Ustr;
28
29pub struct AtomicMap<K, V>(ArcSwap<AHashMap<K, V>>);
40
41impl<K, V> AtomicMap<K, V> {
42 #[must_use]
44 pub fn new() -> Self {
45 Self(ArcSwap::new(Arc::new(AHashMap::new())))
46 }
47
48 #[inline]
53 pub fn load(&self) -> arc_swap::Guard<Arc<AHashMap<K, V>>> {
54 self.0.load()
55 }
56
57 pub fn store(&self, map: AHashMap<K, V>) {
59 self.0.store(Arc::new(map));
60 }
61}
62
63impl<K, V> AtomicMap<K, V>
64where
65 K: Eq + Hash + Clone,
66 V: Clone,
67{
68 pub fn rcu<F>(&self, mut f: F)
73 where
74 F: FnMut(&mut AHashMap<K, V>),
75 {
76 self.0.rcu(|m| {
77 let mut m = (**m).clone();
78 f(&mut m);
79 m
80 });
81 }
82
83 #[inline]
85 pub fn contains_key(&self, key: &K) -> bool {
86 self.0.load().contains_key(key)
87 }
88
89 #[inline]
91 pub fn get_cloned(&self, key: &K) -> Option<V> {
92 self.0.load().get(key).cloned()
93 }
94
95 #[expect(
97 clippy::needless_pass_by_value,
98 reason = "by-value matches HashMap::insert; clone needed because rcu may retry"
99 )]
100 pub fn insert(&self, key: K, value: V) {
101 self.rcu(|m| {
102 m.insert(key.clone(), value.clone());
103 });
104 }
105
106 pub fn remove(&self, key: &K) {
108 self.rcu(|m| {
109 m.remove(key);
110 });
111 }
112
113 #[inline]
115 pub fn len(&self) -> usize {
116 self.0.load().len()
117 }
118
119 #[inline]
121 pub fn is_empty(&self) -> bool {
122 self.0.load().is_empty()
123 }
124}
125
126impl<K, V> Default for AtomicMap<K, V> {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132impl<K: Debug + Eq + Hash, V: Debug> Debug for AtomicMap<K, V> {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 f.debug_map().entries(self.0.load().iter()).finish()
135 }
136}
137
138impl<K: Eq + Hash, V> From<AHashMap<K, V>> for AtomicMap<K, V> {
139 fn from(map: AHashMap<K, V>) -> Self {
140 Self(ArcSwap::new(Arc::new(map)))
141 }
142}
143
144pub struct AtomicSet<K>(ArcSwap<AHashSet<K>>);
155
156impl<K> AtomicSet<K> {
157 #[must_use]
159 pub fn new() -> Self {
160 Self(ArcSwap::new(Arc::new(AHashSet::new())))
161 }
162
163 #[inline]
168 pub fn load(&self) -> arc_swap::Guard<Arc<AHashSet<K>>> {
169 self.0.load()
170 }
171
172 pub fn store(&self, set: AHashSet<K>) {
174 self.0.store(Arc::new(set));
175 }
176}
177
178impl<K> AtomicSet<K>
179where
180 K: Eq + Hash + Clone,
181{
182 pub fn rcu<F>(&self, mut f: F)
187 where
188 F: FnMut(&mut AHashSet<K>),
189 {
190 self.0.rcu(|s| {
191 let mut s = (**s).clone();
192 f(&mut s);
193 s
194 });
195 }
196
197 #[inline]
199 pub fn contains(&self, key: &K) -> bool {
200 self.0.load().contains(key)
201 }
202
203 #[expect(
205 clippy::needless_pass_by_value,
206 reason = "by-value matches HashSet::insert; clone needed because rcu may retry"
207 )]
208 pub fn insert(&self, key: K) {
209 self.rcu(|s| {
210 s.insert(key.clone());
211 });
212 }
213
214 pub fn remove(&self, key: &K) {
216 self.rcu(|s| {
217 s.remove(key);
218 });
219 }
220
221 #[inline]
223 pub fn len(&self) -> usize {
224 self.0.load().len()
225 }
226
227 #[inline]
229 pub fn is_empty(&self) -> bool {
230 self.0.load().is_empty()
231 }
232}
233
234impl<K> Default for AtomicSet<K> {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240impl<K: Debug + Eq + Hash> Debug for AtomicSet<K> {
241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 f.debug_set().entries(self.0.load().iter()).finish()
243 }
244}
245
246impl<K: Eq + Hash> From<AHashSet<K>> for AtomicSet<K> {
247 fn from(set: AHashSet<K>) -> Self {
248 Self(ArcSwap::new(Arc::new(set)))
249 }
250}
251
252pub trait SetLike {
254 type Item: Hash + Eq + Display + Clone;
256
257 fn contains(&self, item: &Self::Item) -> bool;
259 fn is_empty(&self) -> bool;
261}
262
263impl<T, S> SetLike for HashSet<T, S>
264where
265 T: Eq + Hash + Display + Clone,
266 S: std::hash::BuildHasher,
267{
268 type Item = T;
269
270 #[inline]
271 fn contains(&self, v: &T) -> bool {
272 Self::contains(self, v)
273 }
274
275 #[inline]
276 fn is_empty(&self) -> bool {
277 Self::is_empty(self)
278 }
279}
280
281impl<T, S> SetLike for indexmap::IndexSet<T, S>
282where
283 T: Eq + Hash + Display + Clone,
284 S: std::hash::BuildHasher,
285{
286 type Item = T;
287
288 #[inline]
289 fn contains(&self, v: &T) -> bool {
290 Self::contains(self, v)
291 }
292
293 #[inline]
294 fn is_empty(&self) -> bool {
295 Self::is_empty(self)
296 }
297}
298
299impl<T, S> SetLike for ahash::AHashSet<T, S>
300where
301 T: Eq + Hash + Display + Clone,
302 S: std::hash::BuildHasher,
303{
304 type Item = T;
305
306 #[inline]
307 fn contains(&self, v: &T) -> bool {
308 HashSet::contains(self, v)
309 }
310
311 #[inline]
312 fn is_empty(&self) -> bool {
313 HashSet::is_empty(self)
314 }
315}
316
317pub trait MapLike {
319 type Key: Hash + Eq + Display + Clone;
321 type Value: Debug;
323
324 fn contains_key(&self, key: &Self::Key) -> bool;
326 fn is_empty(&self) -> bool;
328}
329
330impl<K, V, S> MapLike for HashMap<K, V, S>
331where
332 K: Eq + Hash + Display + Clone,
333 V: Debug,
334 S: std::hash::BuildHasher,
335{
336 type Key = K;
337 type Value = V;
338
339 #[inline]
340 fn contains_key(&self, k: &K) -> bool {
341 self.contains_key(k)
342 }
343
344 #[inline]
345 fn is_empty(&self) -> bool {
346 self.is_empty()
347 }
348}
349
350impl<K, V, S> MapLike for indexmap::IndexMap<K, V, S>
351where
352 K: Eq + Hash + Display + Clone,
353 V: Debug,
354 S: std::hash::BuildHasher,
355{
356 type Key = K;
357 type Value = V;
358
359 #[inline]
360 fn contains_key(&self, k: &K) -> bool {
361 Self::contains_key(self, k)
362 }
363
364 #[inline]
365 fn is_empty(&self) -> bool {
366 self.is_empty()
367 }
368}
369
370impl<K, V, S> MapLike for ahash::AHashMap<K, V, S>
371where
372 K: Eq + Hash + Display + Clone,
373 V: Debug,
374 S: std::hash::BuildHasher,
375{
376 type Key = K;
377 type Value = V;
378
379 #[inline]
380 fn contains_key(&self, k: &K) -> bool {
381 HashMap::contains_key(self, k)
382 }
383
384 #[inline]
385 fn is_empty(&self) -> bool {
386 HashMap::is_empty(self)
387 }
388}
389
390#[must_use]
392pub fn into_ustr_vec<I, T>(iter: I) -> Vec<Ustr>
393where
394 I: IntoIterator<Item = T>,
395 T: AsRef<str>,
396{
397 iter.into_iter()
398 .map(|item| Ustr::from(item.as_ref()))
399 .collect()
400}
401
402#[cfg(test)]
403#[expect(
404 clippy::unnecessary_to_owned,
405 reason = "Required for trait bound satisfaction"
406)]
407mod tests {
408 use std::{
409 collections::{HashMap, HashSet},
410 sync::{Arc, Barrier},
411 };
412
413 use ahash::{AHashMap, AHashSet};
414 use indexmap::{IndexMap, IndexSet};
415 use rstest::*;
416 use ustr::Ustr;
417
418 use super::*;
419
420 #[rstest]
421 fn test_atomic_set_new_is_empty() {
422 let set: AtomicSet<String> = AtomicSet::new();
423 assert!(set.is_empty());
424 assert_eq!(set.len(), 0);
425 }
426
427 #[rstest]
428 fn test_atomic_set_default_is_empty() {
429 let set: AtomicSet<u64> = AtomicSet::default();
430 assert!(set.is_empty());
431 }
432
433 #[rstest]
434 fn test_atomic_set_insert_and_contains() {
435 let set = AtomicSet::new();
436 set.insert(1);
437 set.insert(2);
438
439 assert!(set.contains(&1));
440 assert!(set.contains(&2));
441 assert!(!set.contains(&3));
442 assert_eq!(set.len(), 2);
443 }
444
445 #[rstest]
446 fn test_atomic_set_insert_duplicate() {
447 let set = AtomicSet::new();
448 set.insert(1);
449 set.insert(1);
450
451 assert_eq!(set.len(), 1);
452 assert!(set.contains(&1));
453 }
454
455 #[rstest]
456 fn test_atomic_set_remove() {
457 let set = AtomicSet::new();
458 set.insert(1);
459 set.insert(2);
460 set.remove(&1);
461
462 assert!(!set.contains(&1));
463 assert!(set.contains(&2));
464 assert_eq!(set.len(), 1);
465 }
466
467 #[rstest]
468 fn test_atomic_set_remove_nonexistent() {
469 let set: AtomicSet<i32> = AtomicSet::new();
470 set.insert(1);
471 set.remove(&999);
472
473 assert_eq!(set.len(), 1);
474 assert!(set.contains(&1));
475 }
476
477 #[rstest]
478 fn test_atomic_set_store_replaces_contents() {
479 let set = AtomicSet::new();
480 set.insert(1);
481 set.insert(2);
482
483 let mut replacement = AHashSet::new();
484 replacement.insert(10);
485 replacement.insert(20);
486 set.store(replacement);
487
488 assert!(!set.contains(&1));
489 assert!(!set.contains(&2));
490 assert!(set.contains(&10));
491 assert!(set.contains(&20));
492 assert_eq!(set.len(), 2);
493 }
494
495 #[rstest]
496 fn test_atomic_set_store_empty_clears() {
497 let set = AtomicSet::new();
498 set.insert(1);
499 set.store(AHashSet::new());
500
501 assert!(set.is_empty());
502 }
503
504 #[rstest]
505 fn test_atomic_set_rcu_batch_insert() {
506 let set = AtomicSet::new();
507 set.rcu(|s| {
508 s.insert(1);
509 s.insert(2);
510 s.insert(3);
511 });
512
513 assert_eq!(set.len(), 3);
514 assert!(set.contains(&1));
515 assert!(set.contains(&2));
516 assert!(set.contains(&3));
517 }
518
519 #[rstest]
520 fn test_atomic_set_rcu_mixed_operations() {
521 let set = AtomicSet::new();
522 set.insert(1);
523 set.insert(2);
524
525 set.rcu(|s| {
526 s.remove(&1);
527 s.insert(3);
528 });
529
530 assert!(!set.contains(&1));
531 assert!(set.contains(&2));
532 assert!(set.contains(&3));
533 }
534
535 #[rstest]
536 fn test_atomic_set_load_returns_snapshot() {
537 let set = AtomicSet::new();
538 set.insert(1);
539
540 let snapshot = set.load();
541 assert!(snapshot.contains(&1));
542 assert_eq!(snapshot.len(), 1);
543 }
544
545 #[rstest]
546 fn test_atomic_set_load_snapshot_not_affected_by_later_writes() {
547 let set = AtomicSet::new();
548 set.insert(1);
549
550 let snapshot = set.load();
551 set.insert(2);
552
553 assert!(!snapshot.contains(&2));
554 assert!(set.contains(&2));
555 }
556
557 #[rstest]
558 fn test_atomic_set_from_ahashset() {
559 let mut source = AHashSet::new();
560 source.insert("a".to_string());
561 source.insert("b".to_string());
562
563 let set = AtomicSet::from(source);
564
565 assert_eq!(set.len(), 2);
566 assert!(set.contains(&"a".to_string()));
567 assert!(set.contains(&"b".to_string()));
568 }
569
570 #[rstest]
571 fn test_atomic_set_debug() {
572 let set = AtomicSet::new();
573 set.insert(42);
574
575 let debug_str = format!("{set:?}");
576 assert!(debug_str.contains("42"));
577 }
578
579 #[rstest]
580 fn test_atomic_set_debug_empty() {
581 let set: AtomicSet<i32> = AtomicSet::new();
582 let debug_str = format!("{set:?}");
583 assert_eq!(debug_str, "{}");
584 }
585
586 #[rstest]
587 fn test_atomic_set_load_iteration() {
588 let set = AtomicSet::new();
589 set.insert(1);
590 set.insert(2);
591 set.insert(3);
592
593 let guard = set.load();
594 let mut values: Vec<_> = guard.iter().copied().collect();
595 values.sort_unstable();
596
597 assert_eq!(values, vec![1, 2, 3]);
598 }
599
600 #[rstest]
601 fn test_atomic_set_concurrent_reads() {
602 let set = Arc::new(AtomicSet::new());
603 for i in 0..100 {
604 set.insert(i);
605 }
606
607 let barrier = Arc::new(Barrier::new(8));
608 let handles: Vec<_> = (0..8)
609 .map(|_| {
610 let set = Arc::clone(&set);
611 let barrier = Arc::clone(&barrier);
612 std::thread::spawn(move || {
613 barrier.wait();
614
615 for i in 0..100 {
616 assert!(set.contains(&i));
617 }
618 })
619 })
620 .collect();
621
622 for h in handles {
623 h.join().unwrap();
624 }
625 }
626
627 #[rstest]
628 fn test_atomic_set_concurrent_rcu_writes() {
629 let set = Arc::new(AtomicSet::new());
630 let barrier = Arc::new(Barrier::new(4));
631
632 let handles: Vec<_> = (0..4u32)
633 .map(|t| {
634 let set = Arc::clone(&set);
635 let barrier = Arc::clone(&barrier);
636 std::thread::spawn(move || {
637 barrier.wait();
638
639 for i in 0..25 {
640 set.insert(t * 25 + i);
641 }
642 })
643 })
644 .collect();
645
646 for h in handles {
647 h.join().unwrap();
648 }
649
650 assert_eq!(set.len(), 100);
651 for i in 0..100 {
652 assert!(set.contains(&i), "missing {i}");
653 }
654 }
655
656 #[rstest]
657 fn test_atomic_set_concurrent_read_write() {
658 let set = Arc::new(AtomicSet::new());
659 for i in 0u32..100 {
660 set.insert(i);
661 }
662
663 let barrier = Arc::new(Barrier::new(5));
664
665 let writer = {
666 let set = Arc::clone(&set);
667 let barrier = Arc::clone(&barrier);
668 std::thread::spawn(move || {
669 barrier.wait();
670
671 for i in 100u32..200 {
672 set.insert(i);
673 }
674 })
675 };
676
677 let readers: Vec<_> = (0..4)
678 .map(|_| {
679 let set = Arc::clone(&set);
680 let barrier = Arc::clone(&barrier);
681 std::thread::spawn(move || {
682 barrier.wait();
683
684 for _ in 0..1000 {
685 let snapshot = set.load();
686 let len = snapshot.len();
687 assert!(
688 (100..=200).contains(&len),
689 "snapshot len {len} outside expected range"
690 );
691
692 for i in 0u32..100 {
693 assert!(snapshot.contains(&i), "original key {i} missing");
694 }
695 }
696 })
697 })
698 .collect();
699
700 writer.join().unwrap();
701 for r in readers {
702 r.join().unwrap();
703 }
704
705 assert_eq!(set.len(), 200);
706 }
707
708 #[rstest]
709 fn test_atomic_set_snapshot_consistency_under_store() {
710 let set = Arc::new(AtomicSet::new());
711 let barrier = Arc::new(Barrier::new(5));
712
713 let writer = {
714 let set = Arc::clone(&set);
715 let barrier = Arc::clone(&barrier);
716 std::thread::spawn(move || {
717 barrier.wait();
718
719 for batch in 0u32..50 {
720 let start = batch * 10;
721 let new_set: AHashSet<u32> = (start..start + 10).collect();
722 set.store(new_set);
723 }
724 })
725 };
726
727 let readers: Vec<_> = (0..4)
728 .map(|_| {
729 let set = Arc::clone(&set);
730 let barrier = Arc::clone(&barrier);
731 std::thread::spawn(move || {
732 barrier.wait();
733
734 for _ in 0..5000 {
735 let snapshot = set.load();
736 let items: Vec<u32> = snapshot.iter().copied().collect();
737 if items.is_empty() {
738 continue;
739 }
740 assert_eq!(
741 items.len(),
742 10,
743 "partial snapshot: got {} items: {items:?}",
744 items.len()
745 );
746 let min = *items.iter().min().unwrap();
747 let max = *items.iter().max().unwrap();
748 assert_eq!(
749 max - min,
750 9,
751 "snapshot not from single batch: min={min} max={max}"
752 );
753 }
754 })
755 })
756 .collect();
757
758 writer.join().unwrap();
759 for r in readers {
760 r.join().unwrap();
761 }
762 }
763
764 #[rstest]
765 fn test_atomic_map_new_is_empty() {
766 let map: AtomicMap<String, i32> = AtomicMap::new();
767 assert!(map.is_empty());
768 assert_eq!(map.len(), 0);
769 }
770
771 #[rstest]
772 fn test_atomic_map_default_is_empty() {
773 let map: AtomicMap<u32, u32> = AtomicMap::default();
774 assert!(map.is_empty());
775 }
776
777 #[rstest]
778 fn test_atomic_map_insert_and_get_cloned() {
779 let map = AtomicMap::new();
780 map.insert("a".to_string(), 1);
781 map.insert("b".to_string(), 2);
782
783 assert_eq!(map.get_cloned(&"a".to_string()), Some(1));
784 assert_eq!(map.get_cloned(&"b".to_string()), Some(2));
785 assert_eq!(map.get_cloned(&"c".to_string()), None);
786 assert_eq!(map.len(), 2);
787 }
788
789 #[rstest]
790 fn test_atomic_map_insert_overwrites() {
791 let map = AtomicMap::new();
792 map.insert("key".to_string(), 1);
793 map.insert("key".to_string(), 2);
794
795 assert_eq!(map.get_cloned(&"key".to_string()), Some(2));
796 assert_eq!(map.len(), 1);
797 }
798
799 #[rstest]
800 fn test_atomic_map_contains_key() {
801 let map = AtomicMap::new();
802 map.insert("present".to_string(), 42);
803
804 assert!(map.contains_key(&"present".to_string()));
805 assert!(!map.contains_key(&"absent".to_string()));
806 }
807
808 #[rstest]
809 fn test_atomic_map_remove() {
810 let map = AtomicMap::new();
811 map.insert("a".to_string(), 1);
812 map.insert("b".to_string(), 2);
813 map.remove(&"a".to_string());
814
815 assert!(!map.contains_key(&"a".to_string()));
816 assert!(map.contains_key(&"b".to_string()));
817 assert_eq!(map.len(), 1);
818 }
819
820 #[rstest]
821 fn test_atomic_map_remove_nonexistent() {
822 let map = AtomicMap::new();
823 map.insert("a".to_string(), 1);
824 map.remove(&"z".to_string());
825
826 assert_eq!(map.len(), 1);
827 }
828
829 #[rstest]
830 fn test_atomic_map_store_replaces_contents() {
831 let map = AtomicMap::new();
832 map.insert("old".to_string(), 1);
833
834 let mut replacement = AHashMap::new();
835 replacement.insert("new".to_string(), 99);
836 map.store(replacement);
837
838 assert!(!map.contains_key(&"old".to_string()));
839 assert_eq!(map.get_cloned(&"new".to_string()), Some(99));
840 }
841
842 #[rstest]
843 fn test_atomic_map_store_empty_clears() {
844 let map = AtomicMap::new();
845 map.insert("key".to_string(), 1);
846 map.store(AHashMap::new());
847
848 assert!(map.is_empty());
849 }
850
851 #[rstest]
852 fn test_atomic_map_rcu_batch_insert() {
853 let map = AtomicMap::new();
854 let entries: Vec<(String, i32)> = (0..5).map(|i| (format!("k{i}"), i)).collect();
855
856 map.rcu(|m| {
857 for (k, v) in &entries {
858 m.insert(k.clone(), *v);
859 }
860 });
861
862 assert_eq!(map.len(), 5);
863 for i in 0..5 {
864 assert_eq!(map.get_cloned(&format!("k{i}")), Some(i));
865 }
866 }
867
868 #[rstest]
869 fn test_atomic_map_rcu_mixed_operations() {
870 let map = AtomicMap::new();
871 map.insert("a".to_string(), 1);
872 map.insert("b".to_string(), 2);
873
874 map.rcu(|m| {
875 m.remove(&"a".to_string());
876 m.insert("c".to_string(), 3);
877 if let Some(v) = m.get_mut(&"b".to_string()) {
878 *v = 20;
879 }
880 });
881
882 assert_eq!(map.get_cloned(&"a".to_string()), None);
883 assert_eq!(map.get_cloned(&"b".to_string()), Some(20));
884 assert_eq!(map.get_cloned(&"c".to_string()), Some(3));
885 }
886
887 #[rstest]
888 fn test_atomic_map_load_returns_snapshot() {
889 let map = AtomicMap::new();
890 map.insert("key".to_string(), 42);
891
892 let snapshot = map.load();
893 assert_eq!(snapshot.get(&"key".to_string()), Some(&42));
894 }
895
896 #[rstest]
897 fn test_atomic_map_load_snapshot_not_affected_by_later_writes() {
898 let map = AtomicMap::new();
899 map.insert("a".to_string(), 1);
900
901 let snapshot = map.load();
902 map.insert("b".to_string(), 2);
903
904 assert!(snapshot.get(&"b".to_string()).is_none());
905 assert_eq!(map.get_cloned(&"b".to_string()), Some(2));
906 }
907
908 #[rstest]
909 fn test_atomic_map_from_ahashmap() {
910 let mut source = AHashMap::new();
911 source.insert(1, "one".to_string());
912 source.insert(2, "two".to_string());
913
914 let map = AtomicMap::from(source);
915
916 assert_eq!(map.len(), 2);
917 assert_eq!(map.get_cloned(&1), Some("one".to_string()));
918 }
919
920 #[rstest]
921 fn test_atomic_map_debug() {
922 let map = AtomicMap::new();
923 map.insert("key".to_string(), 42);
924
925 let debug_str = format!("{map:?}");
926 assert!(debug_str.contains("key"));
927 assert!(debug_str.contains("42"));
928 }
929
930 #[rstest]
931 fn test_atomic_map_debug_empty() {
932 let map: AtomicMap<String, i32> = AtomicMap::new();
933 let debug_str = format!("{map:?}");
934 assert_eq!(debug_str, "{}");
935 }
936
937 #[rstest]
938 fn test_atomic_map_load_iteration() {
939 let map = AtomicMap::new();
940 map.insert(1, 10);
941 map.insert(2, 20);
942 map.insert(3, 30);
943
944 let guard = map.load();
945 let mut pairs: Vec<_> = guard.iter().map(|(k, v)| (*k, *v)).collect();
946 pairs.sort_unstable();
947
948 assert_eq!(pairs, vec![(1, 10), (2, 20), (3, 30)]);
949 }
950
951 #[rstest]
952 fn test_atomic_map_concurrent_reads() {
953 let map = Arc::new(AtomicMap::new());
954 for i in 0u32..100 {
955 map.insert(i, i * 10);
956 }
957
958 let barrier = Arc::new(Barrier::new(8));
959 let handles: Vec<_> = (0..8)
960 .map(|_| {
961 let map = Arc::clone(&map);
962 let barrier = Arc::clone(&barrier);
963 std::thread::spawn(move || {
964 barrier.wait();
965
966 for i in 0u32..100 {
967 assert_eq!(map.get_cloned(&i), Some(i * 10));
968 }
969 })
970 })
971 .collect();
972
973 for h in handles {
974 h.join().unwrap();
975 }
976 }
977
978 #[rstest]
979 fn test_atomic_map_concurrent_rcu_writes() {
980 let map = Arc::new(AtomicMap::new());
981 let barrier = Arc::new(Barrier::new(4));
982
983 let handles: Vec<_> = (0..4u32)
984 .map(|t| {
985 let map = Arc::clone(&map);
986 let barrier = Arc::clone(&barrier);
987 std::thread::spawn(move || {
988 barrier.wait();
989
990 for i in 0..25 {
991 let key = t * 25 + i;
992 map.insert(key, key * 10);
993 }
994 })
995 })
996 .collect();
997
998 for h in handles {
999 h.join().unwrap();
1000 }
1001
1002 assert_eq!(map.len(), 100);
1003 for i in 0u32..100 {
1004 assert_eq!(map.get_cloned(&i), Some(i * 10), "wrong value for {i}");
1005 }
1006 }
1007
1008 #[rstest]
1009 fn test_atomic_map_concurrent_read_write() {
1010 let map = Arc::new(AtomicMap::new());
1011 for i in 0u32..100 {
1012 map.insert(i, i);
1013 }
1014
1015 let barrier = Arc::new(Barrier::new(5));
1016
1017 let writer = {
1018 let map = Arc::clone(&map);
1019 let barrier = Arc::clone(&barrier);
1020 std::thread::spawn(move || {
1021 barrier.wait();
1022
1023 for i in 100u32..200 {
1024 map.insert(i, i);
1025 }
1026 })
1027 };
1028
1029 let readers: Vec<_> = (0..4)
1030 .map(|_| {
1031 let map = Arc::clone(&map);
1032 let barrier = Arc::clone(&barrier);
1033 std::thread::spawn(move || {
1034 barrier.wait();
1035
1036 for _ in 0..1000 {
1037 let snapshot = map.load();
1038 let len = snapshot.len();
1039 assert!(
1040 (100..=200).contains(&len),
1041 "snapshot len {len} outside expected range"
1042 );
1043
1044 for i in 0u32..100 {
1045 assert_eq!(
1046 snapshot.get(&i).copied(),
1047 Some(i),
1048 "original key {i} missing or wrong"
1049 );
1050 }
1051 }
1052 })
1053 })
1054 .collect();
1055
1056 writer.join().unwrap();
1057 for r in readers {
1058 r.join().unwrap();
1059 }
1060
1061 assert_eq!(map.len(), 200);
1062 }
1063
1064 #[rstest]
1065 fn test_atomic_map_snapshot_consistency_under_store() {
1066 let map = Arc::new(AtomicMap::new());
1067 let barrier = Arc::new(Barrier::new(5));
1068
1069 let writer = {
1070 let map = Arc::clone(&map);
1071 let barrier = Arc::clone(&barrier);
1072 std::thread::spawn(move || {
1073 barrier.wait();
1074
1075 for batch in 0u32..50 {
1076 let start = batch * 10;
1077 let new_map: AHashMap<u32, u32> =
1078 (start..start + 10).map(|i| (i, batch)).collect();
1079 map.store(new_map);
1080 }
1081 })
1082 };
1083
1084 let readers: Vec<_> = (0..4)
1085 .map(|_| {
1086 let map = Arc::clone(&map);
1087 let barrier = Arc::clone(&barrier);
1088 std::thread::spawn(move || {
1089 barrier.wait();
1090
1091 for _ in 0..5000 {
1092 let snapshot = map.load();
1093 if snapshot.is_empty() {
1094 continue;
1095 }
1096 let values: AHashSet<u32> = snapshot.values().copied().collect();
1097 assert_eq!(
1098 values.len(),
1099 1,
1100 "snapshot has mixed batch values: {values:?}"
1101 );
1102 assert_eq!(snapshot.len(), 10, "partial snapshot");
1103 }
1104 })
1105 })
1106 .collect();
1107
1108 writer.join().unwrap();
1109 for r in readers {
1110 r.join().unwrap();
1111 }
1112 }
1113
1114 mod proptests {
1115 use proptest::prelude::*;
1116 use rstest::rstest;
1117
1118 use super::*;
1119
1120 #[derive(Debug, Clone)]
1121 enum SetOp {
1122 Insert(u16),
1123 Remove(u16),
1124 Contains(u16),
1125 Len,
1126 IsEmpty,
1127 }
1128
1129 fn set_op_strategy() -> impl Strategy<Value = SetOp> {
1130 prop_oneof![
1131 3 => any::<u16>().prop_map(SetOp::Insert),
1132 3 => any::<u16>().prop_map(SetOp::Remove),
1133 3 => any::<u16>().prop_map(SetOp::Contains),
1134 1 => Just(SetOp::Len),
1135 1 => Just(SetOp::IsEmpty),
1136 ]
1137 }
1138
1139 proptest! {
1140 #![proptest_config(ProptestConfig {
1141 failure_persistence: Some(Box::new(
1142 proptest::test_runner::FileFailurePersistence::WithSource("atomic_set")
1143 )),
1144 cases: 500,
1145 ..ProptestConfig::default()
1146 })]
1147
1148 #[rstest]
1150 fn atomic_set_matches_ahashset(ops in proptest::collection::vec(set_op_strategy(), 0..200)) {
1151 let atomic = AtomicSet::new();
1152 let mut reference = AHashSet::new();
1153
1154 for op in &ops {
1155 match op {
1156 SetOp::Insert(k) => {
1157 atomic.insert(*k);
1158 reference.insert(*k);
1159 }
1160 SetOp::Remove(k) => {
1161 atomic.remove(k);
1162 reference.remove(k);
1163 }
1164 SetOp::Contains(k) => {
1165 prop_assert_eq!(
1166 atomic.contains(k),
1167 reference.contains(k),
1168 "contains mismatch for key {}", k
1169 );
1170 }
1171 SetOp::Len => {
1172 prop_assert_eq!(atomic.len(), reference.len());
1173 }
1174 SetOp::IsEmpty => {
1175 prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1176 }
1177 }
1178 }
1179
1180 prop_assert_eq!(atomic.len(), reference.len());
1181 prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1182
1183 for k in &reference {
1184 prop_assert!(atomic.contains(k), "atomic missing key {}", k);
1185 }
1186 }
1187
1188 #[rstest]
1190 fn atomic_set_store_snapshot(items in proptest::collection::vec(any::<u16>(), 0..100)) {
1191 let set = AtomicSet::new();
1192 set.insert(9999);
1193
1194 let expected: AHashSet<u16> = items.iter().copied().collect();
1195 set.store(expected.clone());
1196
1197 prop_assert_eq!(set.len(), expected.len());
1198 prop_assert!(!set.contains(&9999) || expected.contains(&9999));
1199
1200 for k in &expected {
1201 prop_assert!(set.contains(k));
1202 }
1203 }
1204
1205 #[rstest]
1207 fn atomic_set_rcu_batch(
1208 initial in proptest::collection::vec(any::<u16>(), 0..50),
1209 to_add in proptest::collection::vec(any::<u16>(), 0..50),
1210 to_remove in proptest::collection::vec(any::<u16>(), 0..20),
1211 ) {
1212 let set = AtomicSet::new();
1213 let mut reference = AHashSet::new();
1214
1215 for k in &initial {
1216 set.insert(*k);
1217 reference.insert(*k);
1218 }
1219
1220 let to_add_clone = to_add.clone();
1221 let to_remove_clone = to_remove.clone();
1222 set.rcu(|s| {
1223 for k in &to_add_clone {
1224 s.insert(*k);
1225 }
1226
1227 for k in &to_remove_clone {
1228 s.remove(k);
1229 }
1230 });
1231
1232 for k in &to_add {
1233 reference.insert(*k);
1234 }
1235
1236 for k in &to_remove {
1237 reference.remove(k);
1238 }
1239
1240 prop_assert_eq!(set.len(), reference.len());
1241 for k in &reference {
1242 prop_assert!(set.contains(k));
1243 }
1244 }
1245
1246 #[rstest]
1248 fn atomic_set_snapshot_isolation(
1249 initial in proptest::collection::vec(any::<u16>(), 1..50),
1250 extra in proptest::collection::vec(any::<u16>(), 1..50),
1251 ) {
1252 let set = AtomicSet::new();
1253 let expected: AHashSet<u16> = initial.iter().copied().collect();
1254 for k in &initial {
1255 set.insert(*k);
1256 }
1257
1258 let snapshot = set.load();
1259 let snapshot_contents: AHashSet<u16> = snapshot.iter().copied().collect();
1260 prop_assert_eq!(&snapshot_contents, &expected);
1261
1262 for k in &extra {
1263 set.insert(*k);
1264 }
1265
1266 let snapshot_after: AHashSet<u16> = snapshot.iter().copied().collect();
1267 prop_assert_eq!(&snapshot_contents, &snapshot_after, "snapshot mutated after write");
1268 }
1269
1270 #[rstest]
1272 fn atomic_set_from_roundtrip(items in proptest::collection::vec(any::<u16>(), 0..100)) {
1273 let expected: AHashSet<u16> = items.iter().copied().collect();
1274 let set = AtomicSet::from(expected.clone());
1275
1276 prop_assert_eq!(set.len(), expected.len());
1277 for k in &expected {
1278 prop_assert!(set.contains(k));
1279 }
1280 }
1281 }
1282
1283 #[derive(Debug, Clone)]
1284 enum MapOp {
1285 Insert(u16, u32),
1286 Remove(u16),
1287 GetCloned(u16),
1288 ContainsKey(u16),
1289 Len,
1290 IsEmpty,
1291 LoadGet(u16),
1292 }
1293
1294 fn map_op_strategy() -> impl Strategy<Value = MapOp> {
1295 prop_oneof![
1296 3 => (any::<u16>(), any::<u32>()).prop_map(|(k, v)| MapOp::Insert(k, v)),
1297 3 => any::<u16>().prop_map(MapOp::Remove),
1298 3 => any::<u16>().prop_map(MapOp::GetCloned),
1299 3 => any::<u16>().prop_map(MapOp::ContainsKey),
1300 1 => Just(MapOp::Len),
1301 1 => Just(MapOp::IsEmpty),
1302 3 => any::<u16>().prop_map(MapOp::LoadGet),
1303 ]
1304 }
1305
1306 proptest! {
1307 #![proptest_config(ProptestConfig {
1308 failure_persistence: Some(Box::new(
1309 proptest::test_runner::FileFailurePersistence::WithSource("atomic_map")
1310 )),
1311 cases: 500,
1312 ..ProptestConfig::default()
1313 })]
1314
1315 #[rstest]
1317 fn atomic_map_matches_ahashmap(ops in proptest::collection::vec(map_op_strategy(), 0..200)) {
1318 let atomic = AtomicMap::new();
1319 let mut reference = AHashMap::new();
1320
1321 for op in &ops {
1322 match op {
1323 MapOp::Insert(k, v) => {
1324 atomic.insert(*k, *v);
1325 reference.insert(*k, *v);
1326 }
1327 MapOp::Remove(k) => {
1328 atomic.remove(k);
1329 reference.remove(k);
1330 }
1331 MapOp::GetCloned(k) => {
1332 prop_assert_eq!(
1333 atomic.get_cloned(k),
1334 reference.get(k).copied(),
1335 "get_cloned mismatch for key {}", k
1336 );
1337 }
1338 MapOp::ContainsKey(k) => {
1339 prop_assert_eq!(
1340 atomic.contains_key(k),
1341 reference.contains_key(k),
1342 "contains_key mismatch for key {}", k
1343 );
1344 }
1345 MapOp::Len => {
1346 prop_assert_eq!(atomic.len(), reference.len());
1347 }
1348 MapOp::IsEmpty => {
1349 prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1350 }
1351 MapOp::LoadGet(k) => {
1352 let snapshot = atomic.load();
1353 let via_load = snapshot.get(k).copied();
1354 let via_method = atomic.get_cloned(k);
1355 prop_assert_eq!(
1356 via_load,
1357 reference.get(k).copied(),
1358 "load().get() mismatch for key {}", k
1359 );
1360 prop_assert_eq!(
1361 via_method,
1362 reference.get(k).copied(),
1363 "get_cloned mismatch for key {}", k
1364 );
1365 }
1366 }
1367 }
1368
1369 prop_assert_eq!(atomic.len(), reference.len());
1370 prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1371
1372 for (k, v) in &reference {
1373 prop_assert_eq!(
1374 atomic.get_cloned(k),
1375 Some(*v),
1376 "value mismatch for key {}", k
1377 );
1378 }
1379 }
1380
1381 #[rstest]
1383 fn atomic_map_store_snapshot(
1384 items in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..100),
1385 ) {
1386 let map = AtomicMap::new();
1387 map.insert(9999, 0);
1388
1389 let expected: AHashMap<u16, u32> = items.into_iter().collect();
1390 map.store(expected.clone());
1391
1392 prop_assert_eq!(map.len(), expected.len());
1393 prop_assert!(!map.contains_key(&9999) || expected.contains_key(&9999));
1394
1395 for (k, v) in &expected {
1396 prop_assert_eq!(map.get_cloned(k), Some(*v));
1397 }
1398 }
1399
1400 #[rstest]
1402 fn atomic_map_rcu_batch(
1403 initial in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..50),
1404 to_add in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..50),
1405 to_remove in proptest::collection::vec(any::<u16>(), 0..20),
1406 ) {
1407 let map = AtomicMap::new();
1408 let mut reference = AHashMap::new();
1409
1410 for (k, v) in &initial {
1411 map.insert(*k, *v);
1412 reference.insert(*k, *v);
1413 }
1414
1415 let to_add_clone = to_add.clone();
1416 let to_remove_clone = to_remove.clone();
1417 map.rcu(|m| {
1418 for (k, v) in &to_add_clone {
1419 m.insert(*k, *v);
1420 }
1421
1422 for k in &to_remove_clone {
1423 m.remove(k);
1424 }
1425 });
1426
1427 for (k, v) in &to_add {
1428 reference.insert(*k, *v);
1429 }
1430
1431 for k in &to_remove {
1432 reference.remove(k);
1433 }
1434
1435 prop_assert_eq!(map.len(), reference.len());
1436 for (k, v) in &reference {
1437 prop_assert_eq!(map.get_cloned(k), Some(*v));
1438 }
1439 }
1440
1441 #[rstest]
1443 fn atomic_map_snapshot_isolation(
1444 initial in proptest::collection::vec((any::<u16>(), any::<u32>()), 1..50),
1445 extra in proptest::collection::vec((any::<u16>(), any::<u32>()), 1..50),
1446 ) {
1447 let map = AtomicMap::new();
1448 let expected: AHashMap<u16, u32> = initial.iter().copied().collect();
1449 for (k, v) in &initial {
1450 map.insert(*k, *v);
1451 }
1452
1453 let snapshot = map.load();
1454 let snapshot_contents: AHashMap<u16, u32> =
1455 snapshot.iter().map(|(k, v)| (*k, *v)).collect();
1456 prop_assert_eq!(&snapshot_contents, &expected);
1457
1458 for (k, v) in &extra {
1459 map.insert(*k, *v);
1460 }
1461
1462 let snapshot_after: AHashMap<u16, u32> =
1463 snapshot.iter().map(|(k, v)| (*k, *v)).collect();
1464 prop_assert_eq!(&snapshot_contents, &snapshot_after, "snapshot mutated after write");
1465 }
1466
1467 #[rstest]
1469 fn atomic_map_from_roundtrip(
1470 items in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..100),
1471 ) {
1472 let expected: AHashMap<u16, u32> = items.into_iter().collect();
1473 let map = AtomicMap::from(expected.clone());
1474
1475 prop_assert_eq!(map.len(), expected.len());
1476 for (k, v) in &expected {
1477 prop_assert_eq!(map.get_cloned(k), Some(*v));
1478 }
1479 }
1480 }
1481 }
1482
1483 #[rstest]
1484 fn test_hashset_setlike() {
1485 let mut set: HashSet<String> = HashSet::new();
1486 set.insert("test".to_string());
1487 set.insert("value".to_string());
1488
1489 assert!(set.contains(&"test".to_string()));
1490 assert!(!set.contains(&"missing".to_string()));
1491 assert!(!set.is_empty());
1492
1493 let empty_set: HashSet<String> = HashSet::new();
1494 assert!(empty_set.is_empty());
1495 }
1496
1497 #[rstest]
1498 fn test_indexset_setlike() {
1499 let mut set: IndexSet<String> = IndexSet::new();
1500 set.insert("test".to_string());
1501 set.insert("value".to_string());
1502
1503 assert!(set.contains(&"test".to_string()));
1504 assert!(!set.contains(&"missing".to_string()));
1505 assert!(!set.is_empty());
1506
1507 let empty_set: IndexSet<String> = IndexSet::new();
1508 assert!(empty_set.is_empty());
1509 }
1510
1511 #[rstest]
1512 fn test_into_ustr_vec_from_strings() {
1513 let items = vec!["foo".to_string(), "bar".to_string()];
1514 let ustrs = super::into_ustr_vec(items);
1515
1516 assert_eq!(ustrs.len(), 2);
1517 assert_eq!(ustrs[0], Ustr::from("foo"));
1518 assert_eq!(ustrs[1], Ustr::from("bar"));
1519 }
1520
1521 #[rstest]
1522 fn test_into_ustr_vec_from_str_slices() {
1523 let items = ["alpha", "beta", "gamma"];
1524 let ustrs = super::into_ustr_vec(items);
1525
1526 assert_eq!(ustrs.len(), 3);
1527 assert_eq!(ustrs[2], Ustr::from("gamma"));
1528 }
1529
1530 #[rstest]
1531 fn test_ahashset_setlike() {
1532 let mut set: AHashSet<String> = AHashSet::new();
1533 set.insert("test".to_string());
1534 set.insert("value".to_string());
1535
1536 assert!(set.contains(&"test".to_string()));
1537 assert!(!set.contains(&"missing".to_string()));
1538 assert!(!set.is_empty());
1539
1540 let empty_set: AHashSet<String> = AHashSet::new();
1541 assert!(empty_set.is_empty());
1542 }
1543
1544 #[rstest]
1545 fn test_hashmap_maplike() {
1546 let mut map: HashMap<String, i32> = HashMap::new();
1547 map.insert("key1".to_string(), 42);
1548 map.insert("key2".to_string(), 100);
1549
1550 assert!(map.contains_key(&"key1".to_string()));
1551 assert!(!map.contains_key(&"missing".to_string()));
1552 assert!(!map.is_empty());
1553
1554 let empty_map: HashMap<String, i32> = HashMap::new();
1555 assert!(empty_map.is_empty());
1556 }
1557
1558 #[rstest]
1559 fn test_indexmap_maplike() {
1560 let mut map: IndexMap<String, i32> = IndexMap::new();
1561 map.insert("key1".to_string(), 42);
1562 map.insert("key2".to_string(), 100);
1563
1564 assert!(map.contains_key(&"key1".to_string()));
1565 assert!(!map.contains_key(&"missing".to_string()));
1566 assert!(!map.is_empty());
1567
1568 let empty_map: IndexMap<String, i32> = IndexMap::new();
1569 assert!(empty_map.is_empty());
1570 }
1571
1572 #[rstest]
1573 fn test_ahashmap_maplike() {
1574 let mut map: AHashMap<String, i32> = AHashMap::new();
1575 map.insert("key1".to_string(), 42);
1576 map.insert("key2".to_string(), 100);
1577
1578 assert!(map.contains_key(&"key1".to_string()));
1579 assert!(!map.contains_key(&"missing".to_string()));
1580 assert!(!map.is_empty());
1581
1582 let empty_map: AHashMap<String, i32> = AHashMap::new();
1583 assert!(empty_map.is_empty());
1584 }
1585
1586 #[rstest]
1587 fn test_trait_object_setlike() {
1588 let mut hashset: HashSet<String> = HashSet::new();
1589 hashset.insert("test".to_string());
1590
1591 let mut indexset: IndexSet<String> = IndexSet::new();
1592 indexset.insert("test".to_string());
1593
1594 let sets: Vec<&dyn SetLike<Item = String>> = vec![&hashset, &indexset];
1595
1596 for set in sets {
1597 assert!(set.contains(&"test".to_string()));
1598 assert!(!set.is_empty());
1599 }
1600 }
1601
1602 #[rstest]
1603 fn test_trait_object_maplike() {
1604 let mut hashmap: HashMap<String, i32> = HashMap::new();
1605 hashmap.insert("key".to_string(), 42);
1606
1607 let mut indexmap: IndexMap<String, i32> = IndexMap::new();
1608 indexmap.insert("key".to_string(), 42);
1609
1610 let maps: Vec<&dyn MapLike<Key = String, Value = i32>> = vec![&hashmap, &indexmap];
1611
1612 for map in maps {
1613 assert!(map.contains_key(&"key".to_string()));
1614 assert!(!map.is_empty());
1615 }
1616 }
1617}