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 #[allow(
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 #[allow(
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#[allow(
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 for i in 0..100 {
621 assert!(set.contains(&i));
622 }
623 })
624 })
625 .collect();
626
627 for h in handles {
628 h.join().unwrap();
629 }
630 }
631
632 #[rstest]
633 fn test_atomic_set_concurrent_rcu_writes() {
634 let set = Arc::new(AtomicSet::new());
635 let barrier = Arc::new(Barrier::new(4));
636
637 let handles: Vec<_> = (0..4u32)
638 .map(|t| {
639 let set = Arc::clone(&set);
640 let barrier = Arc::clone(&barrier);
641 std::thread::spawn(move || {
642 barrier.wait();
643 for i in 0..25 {
644 set.insert(t * 25 + i);
645 }
646 })
647 })
648 .collect();
649
650 for h in handles {
651 h.join().unwrap();
652 }
653
654 assert_eq!(set.len(), 100);
655 for i in 0..100 {
656 assert!(set.contains(&i), "missing {i}");
657 }
658 }
659
660 #[rstest]
661 fn test_atomic_set_concurrent_read_write() {
662 let set = Arc::new(AtomicSet::new());
663 for i in 0u32..100 {
664 set.insert(i);
665 }
666
667 let barrier = Arc::new(Barrier::new(5));
668
669 let writer = {
670 let set = Arc::clone(&set);
671 let barrier = Arc::clone(&barrier);
672 std::thread::spawn(move || {
673 barrier.wait();
674 for i in 100u32..200 {
675 set.insert(i);
676 }
677 })
678 };
679
680 let readers: Vec<_> = (0..4)
681 .map(|_| {
682 let set = Arc::clone(&set);
683 let barrier = Arc::clone(&barrier);
684 std::thread::spawn(move || {
685 barrier.wait();
686 for _ in 0..1000 {
687 let snapshot = set.load();
688 let len = snapshot.len();
689 assert!(
690 (100..=200).contains(&len),
691 "snapshot len {len} outside expected range"
692 );
693 for i in 0u32..100 {
694 assert!(snapshot.contains(&i), "original key {i} missing");
695 }
696 }
697 })
698 })
699 .collect();
700
701 writer.join().unwrap();
702 for r in readers {
703 r.join().unwrap();
704 }
705
706 assert_eq!(set.len(), 200);
707 }
708
709 #[rstest]
710 fn test_atomic_set_snapshot_consistency_under_store() {
711 let set = Arc::new(AtomicSet::new());
712 let barrier = Arc::new(Barrier::new(5));
713
714 let writer = {
715 let set = Arc::clone(&set);
716 let barrier = Arc::clone(&barrier);
717 std::thread::spawn(move || {
718 barrier.wait();
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 for _ in 0..5000 {
734 let snapshot = set.load();
735 let items: Vec<u32> = snapshot.iter().copied().collect();
736 if items.is_empty() {
737 continue;
738 }
739 assert_eq!(
740 items.len(),
741 10,
742 "partial snapshot: got {} items: {items:?}",
743 items.len()
744 );
745 let min = *items.iter().min().unwrap();
746 let max = *items.iter().max().unwrap();
747 assert_eq!(
748 max - min,
749 9,
750 "snapshot not from single batch: min={min} max={max}"
751 );
752 }
753 })
754 })
755 .collect();
756
757 writer.join().unwrap();
758 for r in readers {
759 r.join().unwrap();
760 }
761 }
762
763 #[rstest]
764 fn test_atomic_map_new_is_empty() {
765 let map: AtomicMap<String, i32> = AtomicMap::new();
766 assert!(map.is_empty());
767 assert_eq!(map.len(), 0);
768 }
769
770 #[rstest]
771 fn test_atomic_map_default_is_empty() {
772 let map: AtomicMap<u32, u32> = AtomicMap::default();
773 assert!(map.is_empty());
774 }
775
776 #[rstest]
777 fn test_atomic_map_insert_and_get_cloned() {
778 let map = AtomicMap::new();
779 map.insert("a".to_string(), 1);
780 map.insert("b".to_string(), 2);
781
782 assert_eq!(map.get_cloned(&"a".to_string()), Some(1));
783 assert_eq!(map.get_cloned(&"b".to_string()), Some(2));
784 assert_eq!(map.get_cloned(&"c".to_string()), None);
785 assert_eq!(map.len(), 2);
786 }
787
788 #[rstest]
789 fn test_atomic_map_insert_overwrites() {
790 let map = AtomicMap::new();
791 map.insert("key".to_string(), 1);
792 map.insert("key".to_string(), 2);
793
794 assert_eq!(map.get_cloned(&"key".to_string()), Some(2));
795 assert_eq!(map.len(), 1);
796 }
797
798 #[rstest]
799 fn test_atomic_map_contains_key() {
800 let map = AtomicMap::new();
801 map.insert("present".to_string(), 42);
802
803 assert!(map.contains_key(&"present".to_string()));
804 assert!(!map.contains_key(&"absent".to_string()));
805 }
806
807 #[rstest]
808 fn test_atomic_map_remove() {
809 let map = AtomicMap::new();
810 map.insert("a".to_string(), 1);
811 map.insert("b".to_string(), 2);
812 map.remove(&"a".to_string());
813
814 assert!(!map.contains_key(&"a".to_string()));
815 assert!(map.contains_key(&"b".to_string()));
816 assert_eq!(map.len(), 1);
817 }
818
819 #[rstest]
820 fn test_atomic_map_remove_nonexistent() {
821 let map = AtomicMap::new();
822 map.insert("a".to_string(), 1);
823 map.remove(&"z".to_string());
824
825 assert_eq!(map.len(), 1);
826 }
827
828 #[rstest]
829 fn test_atomic_map_store_replaces_contents() {
830 let map = AtomicMap::new();
831 map.insert("old".to_string(), 1);
832
833 let mut replacement = AHashMap::new();
834 replacement.insert("new".to_string(), 99);
835 map.store(replacement);
836
837 assert!(!map.contains_key(&"old".to_string()));
838 assert_eq!(map.get_cloned(&"new".to_string()), Some(99));
839 }
840
841 #[rstest]
842 fn test_atomic_map_store_empty_clears() {
843 let map = AtomicMap::new();
844 map.insert("key".to_string(), 1);
845 map.store(AHashMap::new());
846
847 assert!(map.is_empty());
848 }
849
850 #[rstest]
851 fn test_atomic_map_rcu_batch_insert() {
852 let map = AtomicMap::new();
853 let entries: Vec<(String, i32)> = (0..5).map(|i| (format!("k{i}"), i)).collect();
854
855 map.rcu(|m| {
856 for (k, v) in &entries {
857 m.insert(k.clone(), *v);
858 }
859 });
860
861 assert_eq!(map.len(), 5);
862 for i in 0..5 {
863 assert_eq!(map.get_cloned(&format!("k{i}")), Some(i));
864 }
865 }
866
867 #[rstest]
868 fn test_atomic_map_rcu_mixed_operations() {
869 let map = AtomicMap::new();
870 map.insert("a".to_string(), 1);
871 map.insert("b".to_string(), 2);
872
873 map.rcu(|m| {
874 m.remove(&"a".to_string());
875 m.insert("c".to_string(), 3);
876 if let Some(v) = m.get_mut(&"b".to_string()) {
877 *v = 20;
878 }
879 });
880
881 assert_eq!(map.get_cloned(&"a".to_string()), None);
882 assert_eq!(map.get_cloned(&"b".to_string()), Some(20));
883 assert_eq!(map.get_cloned(&"c".to_string()), Some(3));
884 }
885
886 #[rstest]
887 fn test_atomic_map_load_returns_snapshot() {
888 let map = AtomicMap::new();
889 map.insert("key".to_string(), 42);
890
891 let snapshot = map.load();
892 assert_eq!(snapshot.get(&"key".to_string()), Some(&42));
893 }
894
895 #[rstest]
896 fn test_atomic_map_load_snapshot_not_affected_by_later_writes() {
897 let map = AtomicMap::new();
898 map.insert("a".to_string(), 1);
899
900 let snapshot = map.load();
901 map.insert("b".to_string(), 2);
902
903 assert!(snapshot.get(&"b".to_string()).is_none());
904 assert_eq!(map.get_cloned(&"b".to_string()), Some(2));
905 }
906
907 #[rstest]
908 fn test_atomic_map_from_ahashmap() {
909 let mut source = AHashMap::new();
910 source.insert(1, "one".to_string());
911 source.insert(2, "two".to_string());
912
913 let map = AtomicMap::from(source);
914
915 assert_eq!(map.len(), 2);
916 assert_eq!(map.get_cloned(&1), Some("one".to_string()));
917 }
918
919 #[rstest]
920 fn test_atomic_map_debug() {
921 let map = AtomicMap::new();
922 map.insert("key".to_string(), 42);
923
924 let debug_str = format!("{map:?}");
925 assert!(debug_str.contains("key"));
926 assert!(debug_str.contains("42"));
927 }
928
929 #[rstest]
930 fn test_atomic_map_debug_empty() {
931 let map: AtomicMap<String, i32> = AtomicMap::new();
932 let debug_str = format!("{map:?}");
933 assert_eq!(debug_str, "{}");
934 }
935
936 #[rstest]
937 fn test_atomic_map_load_iteration() {
938 let map = AtomicMap::new();
939 map.insert(1, 10);
940 map.insert(2, 20);
941 map.insert(3, 30);
942
943 let guard = map.load();
944 let mut pairs: Vec<_> = guard.iter().map(|(k, v)| (*k, *v)).collect();
945 pairs.sort_unstable();
946
947 assert_eq!(pairs, vec![(1, 10), (2, 20), (3, 30)]);
948 }
949
950 #[rstest]
951 fn test_atomic_map_concurrent_reads() {
952 let map = Arc::new(AtomicMap::new());
953 for i in 0u32..100 {
954 map.insert(i, i * 10);
955 }
956
957 let barrier = Arc::new(Barrier::new(8));
958 let handles: Vec<_> = (0..8)
959 .map(|_| {
960 let map = Arc::clone(&map);
961 let barrier = Arc::clone(&barrier);
962 std::thread::spawn(move || {
963 barrier.wait();
964 for i in 0u32..100 {
965 assert_eq!(map.get_cloned(&i), Some(i * 10));
966 }
967 })
968 })
969 .collect();
970
971 for h in handles {
972 h.join().unwrap();
973 }
974 }
975
976 #[rstest]
977 fn test_atomic_map_concurrent_rcu_writes() {
978 let map = Arc::new(AtomicMap::new());
979 let barrier = Arc::new(Barrier::new(4));
980
981 let handles: Vec<_> = (0..4u32)
982 .map(|t| {
983 let map = Arc::clone(&map);
984 let barrier = Arc::clone(&barrier);
985 std::thread::spawn(move || {
986 barrier.wait();
987 for i in 0..25 {
988 let key = t * 25 + i;
989 map.insert(key, key * 10);
990 }
991 })
992 })
993 .collect();
994
995 for h in handles {
996 h.join().unwrap();
997 }
998
999 assert_eq!(map.len(), 100);
1000 for i in 0u32..100 {
1001 assert_eq!(map.get_cloned(&i), Some(i * 10), "wrong value for {i}");
1002 }
1003 }
1004
1005 #[rstest]
1006 fn test_atomic_map_concurrent_read_write() {
1007 let map = Arc::new(AtomicMap::new());
1008 for i in 0u32..100 {
1009 map.insert(i, i);
1010 }
1011
1012 let barrier = Arc::new(Barrier::new(5));
1013
1014 let writer = {
1015 let map = Arc::clone(&map);
1016 let barrier = Arc::clone(&barrier);
1017 std::thread::spawn(move || {
1018 barrier.wait();
1019 for i in 100u32..200 {
1020 map.insert(i, i);
1021 }
1022 })
1023 };
1024
1025 let readers: Vec<_> = (0..4)
1026 .map(|_| {
1027 let map = Arc::clone(&map);
1028 let barrier = Arc::clone(&barrier);
1029 std::thread::spawn(move || {
1030 barrier.wait();
1031 for _ in 0..1000 {
1032 let snapshot = map.load();
1033 let len = snapshot.len();
1034 assert!(
1035 (100..=200).contains(&len),
1036 "snapshot len {len} outside expected range"
1037 );
1038 for i in 0u32..100 {
1039 assert_eq!(
1040 snapshot.get(&i).copied(),
1041 Some(i),
1042 "original key {i} missing or wrong"
1043 );
1044 }
1045 }
1046 })
1047 })
1048 .collect();
1049
1050 writer.join().unwrap();
1051 for r in readers {
1052 r.join().unwrap();
1053 }
1054
1055 assert_eq!(map.len(), 200);
1056 }
1057
1058 #[rstest]
1059 fn test_atomic_map_snapshot_consistency_under_store() {
1060 let map = Arc::new(AtomicMap::new());
1061 let barrier = Arc::new(Barrier::new(5));
1062
1063 let writer = {
1064 let map = Arc::clone(&map);
1065 let barrier = Arc::clone(&barrier);
1066 std::thread::spawn(move || {
1067 barrier.wait();
1068 for batch in 0u32..50 {
1069 let start = batch * 10;
1070 let new_map: AHashMap<u32, u32> =
1071 (start..start + 10).map(|i| (i, batch)).collect();
1072 map.store(new_map);
1073 }
1074 })
1075 };
1076
1077 let readers: Vec<_> = (0..4)
1078 .map(|_| {
1079 let map = Arc::clone(&map);
1080 let barrier = Arc::clone(&barrier);
1081 std::thread::spawn(move || {
1082 barrier.wait();
1083 for _ in 0..5000 {
1084 let snapshot = map.load();
1085 if snapshot.is_empty() {
1086 continue;
1087 }
1088 let values: AHashSet<u32> = snapshot.values().copied().collect();
1089 assert_eq!(
1090 values.len(),
1091 1,
1092 "snapshot has mixed batch values: {values:?}"
1093 );
1094 assert_eq!(snapshot.len(), 10, "partial snapshot");
1095 }
1096 })
1097 })
1098 .collect();
1099
1100 writer.join().unwrap();
1101 for r in readers {
1102 r.join().unwrap();
1103 }
1104 }
1105
1106 mod proptests {
1107 use proptest::prelude::*;
1108 use rstest::rstest;
1109
1110 use super::*;
1111
1112 #[derive(Debug, Clone)]
1113 enum SetOp {
1114 Insert(u16),
1115 Remove(u16),
1116 Contains(u16),
1117 Len,
1118 IsEmpty,
1119 }
1120
1121 fn set_op_strategy() -> impl Strategy<Value = SetOp> {
1122 prop_oneof![
1123 3 => any::<u16>().prop_map(SetOp::Insert),
1124 3 => any::<u16>().prop_map(SetOp::Remove),
1125 3 => any::<u16>().prop_map(SetOp::Contains),
1126 1 => Just(SetOp::Len),
1127 1 => Just(SetOp::IsEmpty),
1128 ]
1129 }
1130
1131 proptest! {
1132 #![proptest_config(ProptestConfig {
1133 failure_persistence: Some(Box::new(
1134 proptest::test_runner::FileFailurePersistence::WithSource("atomic_set")
1135 )),
1136 cases: 500,
1137 ..ProptestConfig::default()
1138 })]
1139
1140 #[rstest]
1142 fn atomic_set_matches_ahashset(ops in proptest::collection::vec(set_op_strategy(), 0..200)) {
1143 let atomic = AtomicSet::new();
1144 let mut reference = AHashSet::new();
1145
1146 for op in &ops {
1147 match op {
1148 SetOp::Insert(k) => {
1149 atomic.insert(*k);
1150 reference.insert(*k);
1151 }
1152 SetOp::Remove(k) => {
1153 atomic.remove(k);
1154 reference.remove(k);
1155 }
1156 SetOp::Contains(k) => {
1157 prop_assert_eq!(
1158 atomic.contains(k),
1159 reference.contains(k),
1160 "contains mismatch for key {}", k
1161 );
1162 }
1163 SetOp::Len => {
1164 prop_assert_eq!(atomic.len(), reference.len());
1165 }
1166 SetOp::IsEmpty => {
1167 prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1168 }
1169 }
1170 }
1171
1172 prop_assert_eq!(atomic.len(), reference.len());
1173 prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1174
1175 for k in &reference {
1176 prop_assert!(atomic.contains(k), "atomic missing key {}", k);
1177 }
1178 }
1179
1180 #[rstest]
1182 fn atomic_set_store_snapshot(items in proptest::collection::vec(any::<u16>(), 0..100)) {
1183 let set = AtomicSet::new();
1184 set.insert(9999);
1185
1186 let expected: AHashSet<u16> = items.iter().copied().collect();
1187 set.store(expected.clone());
1188
1189 prop_assert_eq!(set.len(), expected.len());
1190 prop_assert!(!set.contains(&9999) || expected.contains(&9999));
1191
1192 for k in &expected {
1193 prop_assert!(set.contains(k));
1194 }
1195 }
1196
1197 #[rstest]
1199 fn atomic_set_rcu_batch(
1200 initial in proptest::collection::vec(any::<u16>(), 0..50),
1201 to_add in proptest::collection::vec(any::<u16>(), 0..50),
1202 to_remove in proptest::collection::vec(any::<u16>(), 0..20),
1203 ) {
1204 let set = AtomicSet::new();
1205 let mut reference = AHashSet::new();
1206
1207 for k in &initial {
1208 set.insert(*k);
1209 reference.insert(*k);
1210 }
1211
1212 let to_add_clone = to_add.clone();
1213 let to_remove_clone = to_remove.clone();
1214 set.rcu(|s| {
1215 for k in &to_add_clone {
1216 s.insert(*k);
1217 }
1218 for k in &to_remove_clone {
1219 s.remove(k);
1220 }
1221 });
1222
1223 for k in &to_add {
1224 reference.insert(*k);
1225 }
1226 for k in &to_remove {
1227 reference.remove(k);
1228 }
1229
1230 prop_assert_eq!(set.len(), reference.len());
1231 for k in &reference {
1232 prop_assert!(set.contains(k));
1233 }
1234 }
1235
1236 #[rstest]
1238 fn atomic_set_snapshot_isolation(
1239 initial in proptest::collection::vec(any::<u16>(), 1..50),
1240 extra in proptest::collection::vec(any::<u16>(), 1..50),
1241 ) {
1242 let set = AtomicSet::new();
1243 for k in &initial {
1244 set.insert(*k);
1245 }
1246
1247 let snapshot = set.load();
1248 let snapshot_contents: AHashSet<u16> = snapshot.iter().copied().collect();
1249
1250 for k in &extra {
1251 set.insert(*k);
1252 }
1253
1254 let snapshot_after: AHashSet<u16> = snapshot.iter().copied().collect();
1255 prop_assert_eq!(snapshot_contents, snapshot_after, "snapshot mutated after write");
1256 }
1257
1258 #[rstest]
1260 fn atomic_set_from_roundtrip(items in proptest::collection::vec(any::<u16>(), 0..100)) {
1261 let expected: AHashSet<u16> = items.iter().copied().collect();
1262 let set = AtomicSet::from(expected.clone());
1263
1264 prop_assert_eq!(set.len(), expected.len());
1265 for k in &expected {
1266 prop_assert!(set.contains(k));
1267 }
1268 }
1269 }
1270
1271 #[derive(Debug, Clone)]
1272 enum MapOp {
1273 Insert(u16, u32),
1274 Remove(u16),
1275 GetCloned(u16),
1276 ContainsKey(u16),
1277 Len,
1278 IsEmpty,
1279 LoadGet(u16),
1280 }
1281
1282 fn map_op_strategy() -> impl Strategy<Value = MapOp> {
1283 prop_oneof![
1284 3 => (any::<u16>(), any::<u32>()).prop_map(|(k, v)| MapOp::Insert(k, v)),
1285 3 => any::<u16>().prop_map(MapOp::Remove),
1286 3 => any::<u16>().prop_map(MapOp::GetCloned),
1287 3 => any::<u16>().prop_map(MapOp::ContainsKey),
1288 1 => Just(MapOp::Len),
1289 1 => Just(MapOp::IsEmpty),
1290 3 => any::<u16>().prop_map(MapOp::LoadGet),
1291 ]
1292 }
1293
1294 proptest! {
1295 #![proptest_config(ProptestConfig {
1296 failure_persistence: Some(Box::new(
1297 proptest::test_runner::FileFailurePersistence::WithSource("atomic_map")
1298 )),
1299 cases: 500,
1300 ..ProptestConfig::default()
1301 })]
1302
1303 #[rstest]
1305 fn atomic_map_matches_ahashmap(ops in proptest::collection::vec(map_op_strategy(), 0..200)) {
1306 let atomic = AtomicMap::new();
1307 let mut reference = AHashMap::new();
1308
1309 for op in &ops {
1310 match op {
1311 MapOp::Insert(k, v) => {
1312 atomic.insert(*k, *v);
1313 reference.insert(*k, *v);
1314 }
1315 MapOp::Remove(k) => {
1316 atomic.remove(k);
1317 reference.remove(k);
1318 }
1319 MapOp::GetCloned(k) => {
1320 prop_assert_eq!(
1321 atomic.get_cloned(k),
1322 reference.get(k).copied(),
1323 "get_cloned mismatch for key {}", k
1324 );
1325 }
1326 MapOp::ContainsKey(k) => {
1327 prop_assert_eq!(
1328 atomic.contains_key(k),
1329 reference.contains_key(k),
1330 "contains_key mismatch for key {}", k
1331 );
1332 }
1333 MapOp::Len => {
1334 prop_assert_eq!(atomic.len(), reference.len());
1335 }
1336 MapOp::IsEmpty => {
1337 prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1338 }
1339 MapOp::LoadGet(k) => {
1340 let snapshot = atomic.load();
1341 let via_load = snapshot.get(k).copied();
1342 let via_method = atomic.get_cloned(k);
1343 prop_assert_eq!(
1344 via_load,
1345 reference.get(k).copied(),
1346 "load().get() mismatch for key {}", k
1347 );
1348 prop_assert_eq!(
1349 via_method,
1350 reference.get(k).copied(),
1351 "get_cloned mismatch for key {}", k
1352 );
1353 }
1354 }
1355 }
1356
1357 prop_assert_eq!(atomic.len(), reference.len());
1358 prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1359
1360 for (k, v) in &reference {
1361 prop_assert_eq!(
1362 atomic.get_cloned(k),
1363 Some(*v),
1364 "value mismatch for key {}", k
1365 );
1366 }
1367 }
1368
1369 #[rstest]
1371 fn atomic_map_store_snapshot(
1372 items in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..100),
1373 ) {
1374 let map = AtomicMap::new();
1375 map.insert(9999, 0);
1376
1377 let expected: AHashMap<u16, u32> = items.into_iter().collect();
1378 map.store(expected.clone());
1379
1380 prop_assert_eq!(map.len(), expected.len());
1381 prop_assert!(!map.contains_key(&9999) || expected.contains_key(&9999));
1382
1383 for (k, v) in &expected {
1384 prop_assert_eq!(map.get_cloned(k), Some(*v));
1385 }
1386 }
1387
1388 #[rstest]
1390 fn atomic_map_rcu_batch(
1391 initial in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..50),
1392 to_add in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..50),
1393 to_remove in proptest::collection::vec(any::<u16>(), 0..20),
1394 ) {
1395 let map = AtomicMap::new();
1396 let mut reference = AHashMap::new();
1397
1398 for (k, v) in &initial {
1399 map.insert(*k, *v);
1400 reference.insert(*k, *v);
1401 }
1402
1403 let to_add_clone = to_add.clone();
1404 let to_remove_clone = to_remove.clone();
1405 map.rcu(|m| {
1406 for (k, v) in &to_add_clone {
1407 m.insert(*k, *v);
1408 }
1409 for k in &to_remove_clone {
1410 m.remove(k);
1411 }
1412 });
1413
1414 for (k, v) in &to_add {
1415 reference.insert(*k, *v);
1416 }
1417 for k in &to_remove {
1418 reference.remove(k);
1419 }
1420
1421 prop_assert_eq!(map.len(), reference.len());
1422 for (k, v) in &reference {
1423 prop_assert_eq!(map.get_cloned(k), Some(*v));
1424 }
1425 }
1426
1427 #[rstest]
1429 fn atomic_map_snapshot_isolation(
1430 initial in proptest::collection::vec((any::<u16>(), any::<u32>()), 1..50),
1431 extra in proptest::collection::vec((any::<u16>(), any::<u32>()), 1..50),
1432 ) {
1433 let map = AtomicMap::new();
1434 for (k, v) in &initial {
1435 map.insert(*k, *v);
1436 }
1437
1438 let snapshot = map.load();
1439 let snapshot_contents: AHashMap<u16, u32> =
1440 snapshot.iter().map(|(k, v)| (*k, *v)).collect();
1441
1442 for (k, v) in &extra {
1443 map.insert(*k, *v);
1444 }
1445
1446 let snapshot_after: AHashMap<u16, u32> =
1447 snapshot.iter().map(|(k, v)| (*k, *v)).collect();
1448 prop_assert_eq!(snapshot_contents, snapshot_after, "snapshot mutated after write");
1449 }
1450
1451 #[rstest]
1453 fn atomic_map_from_roundtrip(
1454 items in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..100),
1455 ) {
1456 let expected: AHashMap<u16, u32> = items.into_iter().collect();
1457 let map = AtomicMap::from(expected.clone());
1458
1459 prop_assert_eq!(map.len(), expected.len());
1460 for (k, v) in &expected {
1461 prop_assert_eq!(map.get_cloned(k), Some(*v));
1462 }
1463 }
1464 }
1465 }
1466
1467 #[rstest]
1468 fn test_hashset_setlike() {
1469 let mut set: HashSet<String> = HashSet::new();
1470 set.insert("test".to_string());
1471 set.insert("value".to_string());
1472
1473 assert!(set.contains(&"test".to_string()));
1474 assert!(!set.contains(&"missing".to_string()));
1475 assert!(!set.is_empty());
1476
1477 let empty_set: HashSet<String> = HashSet::new();
1478 assert!(empty_set.is_empty());
1479 }
1480
1481 #[rstest]
1482 fn test_indexset_setlike() {
1483 let mut set: IndexSet<String> = IndexSet::new();
1484 set.insert("test".to_string());
1485 set.insert("value".to_string());
1486
1487 assert!(set.contains(&"test".to_string()));
1488 assert!(!set.contains(&"missing".to_string()));
1489 assert!(!set.is_empty());
1490
1491 let empty_set: IndexSet<String> = IndexSet::new();
1492 assert!(empty_set.is_empty());
1493 }
1494
1495 #[rstest]
1496 fn test_into_ustr_vec_from_strings() {
1497 let items = vec!["foo".to_string(), "bar".to_string()];
1498 let ustrs = super::into_ustr_vec(items);
1499
1500 assert_eq!(ustrs.len(), 2);
1501 assert_eq!(ustrs[0], Ustr::from("foo"));
1502 assert_eq!(ustrs[1], Ustr::from("bar"));
1503 }
1504
1505 #[rstest]
1506 fn test_into_ustr_vec_from_str_slices() {
1507 let items = ["alpha", "beta", "gamma"];
1508 let ustrs = super::into_ustr_vec(items);
1509
1510 assert_eq!(ustrs.len(), 3);
1511 assert_eq!(ustrs[2], Ustr::from("gamma"));
1512 }
1513
1514 #[rstest]
1515 fn test_ahashset_setlike() {
1516 let mut set: AHashSet<String> = AHashSet::new();
1517 set.insert("test".to_string());
1518 set.insert("value".to_string());
1519
1520 assert!(set.contains(&"test".to_string()));
1521 assert!(!set.contains(&"missing".to_string()));
1522 assert!(!set.is_empty());
1523
1524 let empty_set: AHashSet<String> = AHashSet::new();
1525 assert!(empty_set.is_empty());
1526 }
1527
1528 #[rstest]
1529 fn test_hashmap_maplike() {
1530 let mut map: HashMap<String, i32> = HashMap::new();
1531 map.insert("key1".to_string(), 42);
1532 map.insert("key2".to_string(), 100);
1533
1534 assert!(map.contains_key(&"key1".to_string()));
1535 assert!(!map.contains_key(&"missing".to_string()));
1536 assert!(!map.is_empty());
1537
1538 let empty_map: HashMap<String, i32> = HashMap::new();
1539 assert!(empty_map.is_empty());
1540 }
1541
1542 #[rstest]
1543 fn test_indexmap_maplike() {
1544 let mut map: IndexMap<String, i32> = IndexMap::new();
1545 map.insert("key1".to_string(), 42);
1546 map.insert("key2".to_string(), 100);
1547
1548 assert!(map.contains_key(&"key1".to_string()));
1549 assert!(!map.contains_key(&"missing".to_string()));
1550 assert!(!map.is_empty());
1551
1552 let empty_map: IndexMap<String, i32> = IndexMap::new();
1553 assert!(empty_map.is_empty());
1554 }
1555
1556 #[rstest]
1557 fn test_ahashmap_maplike() {
1558 let mut map: AHashMap<String, i32> = AHashMap::new();
1559 map.insert("key1".to_string(), 42);
1560 map.insert("key2".to_string(), 100);
1561
1562 assert!(map.contains_key(&"key1".to_string()));
1563 assert!(!map.contains_key(&"missing".to_string()));
1564 assert!(!map.is_empty());
1565
1566 let empty_map: AHashMap<String, i32> = AHashMap::new();
1567 assert!(empty_map.is_empty());
1568 }
1569
1570 #[rstest]
1571 fn test_trait_object_setlike() {
1572 let mut hashset: HashSet<String> = HashSet::new();
1573 hashset.insert("test".to_string());
1574
1575 let mut indexset: IndexSet<String> = IndexSet::new();
1576 indexset.insert("test".to_string());
1577
1578 let sets: Vec<&dyn SetLike<Item = String>> = vec![&hashset, &indexset];
1579
1580 for set in sets {
1581 assert!(set.contains(&"test".to_string()));
1582 assert!(!set.is_empty());
1583 }
1584 }
1585
1586 #[rstest]
1587 fn test_trait_object_maplike() {
1588 let mut hashmap: HashMap<String, i32> = HashMap::new();
1589 hashmap.insert("key".to_string(), 42);
1590
1591 let mut indexmap: IndexMap<String, i32> = IndexMap::new();
1592 indexmap.insert("key".to_string(), 42);
1593
1594 let maps: Vec<&dyn MapLike<Key = String, Value = i32>> = vec![&hashmap, &indexmap];
1595
1596 for map in maps {
1597 assert!(map.contains_key(&"key".to_string()));
1598 assert!(!map.is_empty());
1599 }
1600 }
1601}