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