@@ -618,6 +618,11 @@ private:
618618 InnerMap *volatile read_map_;
619619 std::size_t miss_time_;
620620
621+ // RCU reader count: incremented by readers before accessing read_map_,
622+ // decremented after the read is complete. Writers (CheckSwapInLock, CheckGc)
623+ // wait for this to reach 0 before freeing old maps, preventing use-after-free.
624+ mutable std::atomic<u64 > rcu_reader_count_{0 };
625+
621626 mutable std::mutex dirty_lock_;
622627 InnerMap *dirty_map_;
623628
@@ -662,6 +667,9 @@ typename RcuMap<Key, Value>::MapValue RcuMap<Key, Value>::CreateMapValue(const V
662667
663668template <typename Key, typename Value>
664669Value *RcuMap<Key, Value>::Get(const Key &key, bool update_access_time) {
670+ // RCU read protection: prevent write-side GC from freeing read_map_ during iteration
671+ rcu_reader_count_.fetch_add (1 , std::memory_order_acquire);
672+
665673 // ULTRA-OPTIMIZED RCU READ PATTERN FOR READ-HEAVY WORKLOADS:
666674 // Eliminate ALL overhead in the common case to match MapWithLock performance
667675
@@ -686,9 +694,13 @@ Value *RcuMap<Key, Value>::Get(const Key &key, bool update_access_time) {
686694 }
687695 }
688696 }
697+ rcu_reader_count_.fetch_sub (1 , std::memory_order_release);
689698 return &(it->second .value_ );
690699 }
691700
701+ // RCU: temporarily release reader count while checking dirty_map under lock
702+ rcu_reader_count_.fetch_sub (1 , std::memory_order_release);
703+
692704 // PHASE 2: Check dirty_map for recent writes (SLOW PATH - MINIMIZED)
693705 // This path should be rare in read-heavy workloads
694706 {
@@ -740,21 +752,25 @@ std::optional<Value> RcuMap<Key, Value>::GetValue(const Key &key, bool update_ac
740752
741753template <typename Key, typename Value>
742754Value *RcuMap<Key, Value>::GetWithRcuTime(const Key &key) {
755+ // RCU read protection
756+ rcu_reader_count_.fetch_add (1 , std::memory_order_acquire);
757+
743758 // ABSOLUTE FASTEST PATH: Zero overhead reads for maximum performance
744- // This should be as fast as MapWithLock::Get() with shared_lock
745759 InnerMap *current_read = read_map_;
746760 auto it = current_read->find (key);
747761
748762 if (it != current_read->end ()) {
763+ rcu_reader_count_.fetch_sub (1 , std::memory_order_release);
749764 return &(it->second .value_ );
750765 }
751766
767+ rcu_reader_count_.fetch_sub (1 , std::memory_order_release);
768+
752769 // Inline dirty_map check to avoid function call overhead
753770 {
754771 std::lock_guard<std::mutex> lock (dirty_lock_);
755772 auto dirty_it = dirty_map_->find (key);
756773 if (dirty_it != dirty_map_->end ()) {
757- // No access time updates, no miss tracking - pure read performance
758774 return &(dirty_it->second .value_ );
759775 }
760776 }
@@ -764,18 +780,18 @@ Value *RcuMap<Key, Value>::GetWithRcuTime(const Key &key) {
764780
765781template <typename Key, typename Value>
766782Value *RcuMap<Key, Value>::GetReadOnly(const Key &key) {
767- // BENCHMARK-OPTIMIZED READ: Absolute minimum overhead
768- // This method is designed to match MapWithLock performance exactly
769- // by eliminating ALL RCU-specific overhead
783+ // RCU read protection
784+ rcu_reader_count_.fetch_add (1 , std::memory_order_acquire);
770785
771- // Try read_map first (should succeed in most cases for read-heavy workloads)
772786 InnerMap *current_read = read_map_;
773787 auto it = current_read->find (key);
774788 if (it != current_read->end ()) {
789+ rcu_reader_count_.fetch_sub (1 , std::memory_order_release);
775790 return &(it->second .value_ );
776791 }
777792
778- // If not found, try dirty_map (minimal overhead)
793+ rcu_reader_count_.fetch_sub (1 , std::memory_order_release);
794+
779795 std::lock_guard<std::mutex> lock (dirty_lock_);
780796 auto dirty_it = dirty_map_->find (key);
781797 if (dirty_it != dirty_map_->end ()) {
@@ -826,6 +842,12 @@ void RcuMap<Key, Value>::CheckSwapInLock() {
826842 // std::set up new dirty_map for future writes
827843 dirty_map_ = new_dirty_map;
828844
845+ // RCU SYNCHRONIZATION: Wait for all readers that saw the OLD read_map_ to finish.
846+ // Without this, a reader could still be iterating the old map when CheckGc frees it.
847+ while (rcu_reader_count_.load (std::memory_order_acquire) > 0 ) {
848+ std::this_thread::yield ();
849+ }
850+
829851 // Schedule old read_map for garbage collection
830852 // Cannot delete immediately - readers may still be using it
831853 deleted_map_list_.push_back (deleted_map);
@@ -929,6 +951,12 @@ void RcuMap<Key, Value>::CheckGc(u64 min_delete_time) {
929951 }
930952
931953 for (auto &deleted_map : map_need_delete) {
954+ // RCU: Wait for any in-flight readers to finish before freeing old maps.
955+ // CheckSwapInLock already waited, but there may be readers that started before
956+ // the swap and haven't finished yet.
957+ while (rcu_reader_count_.load (std::memory_order_acquire) > 0 ) {
958+ std::this_thread::yield ();
959+ }
932960 delete deleted_map.deleted_entries_ ;
933961 delete deleted_map.map_ ;
934962 }
@@ -991,20 +1019,31 @@ void RcuMap<Key, Value>::emplace(const Key &key, Args &&...args) {
9911019
9921020template <typename Key, typename Value>
9931021u32 RcuMap<Key, Value>::range(const Key &key_min, const Key &key_max, std::vector<Value> &result) const {
994- InnerMap *current_read = read_map_;
995- auto read_begin = current_read->lower_bound (key_min);
996- auto read_end = current_read->upper_bound (key_max);
997-
1022+ // 1. Acquire dirty snapshot under lock FIRST — avoids lock-ordering inversion
1023+ // where CheckSwapInLock holds dirty_lock_ and waits for readers to drain.
9981024 InnerMap *dirty_snapshot = nullptr ;
9991025 {
10001026 std::lock_guard<std::mutex> lock (dirty_lock_);
10011027 dirty_snapshot = dirty_map_;
10021028 }
10031029
1030+ // 2. Now pin the reader count (read_map_ is stable)
1031+ rcu_reader_count_.fetch_add (1 , std::memory_order_acquire);
1032+
1033+ // RAII: release reader count on any exit path (normal or exceptional)
1034+ struct RcuReaderGuard {
1035+ std::atomic<u64 > &cnt_;
1036+ ~RcuReaderGuard () { cnt_.fetch_sub (1 , std::memory_order_release); }
1037+ } guard{rcu_reader_count_};
1038+
1039+ InnerMap *current_read = read_map_;
1040+ auto read_begin = current_read->lower_bound (key_min);
1041+ auto read_end = current_read->upper_bound (key_max);
1042+
10041043 auto dirty_begin = dirty_snapshot->lower_bound (key_min);
10051044 auto dirty_end = dirty_snapshot->upper_bound (key_max);
10061045
1007- // merge - for std::map, we prioritize dirty_map values over read_map
1046+ // merge — dirty_map values take priority over read_map
10081047 u32 count = 0 ;
10091048 auto read_it = read_begin;
10101049 auto dirty_it = dirty_begin;
@@ -1028,6 +1067,7 @@ u32 RcuMap<Key, Value>::range(const Key &key_min, const Key &key_max, std::vecto
10281067 }
10291068
10301069 return count;
1070+ // ~RcuReaderGuard releases reader count
10311071}
10321072
10331073// MapWithLock compatibility methods implementation
@@ -1081,16 +1121,26 @@ template <typename Key, typename Value>
10811121void RcuMap<Key, Value>::Range(const Key &key_min, const Key &key_max, std::vector<std::pair<Key, Value>> &items) {
10821122 items.clear ();
10831123
1084- InnerMap *current_read = read_map_;
1085- auto read_begin = current_read->lower_bound (key_min);
1086- auto read_end = current_read->upper_bound (key_max);
1087-
1124+ // 1. Acquire dirty snapshot under lock FIRST — avoids lock-ordering inversion
10881125 InnerMap *dirty_snapshot = nullptr ;
10891126 {
10901127 std::lock_guard<std::mutex> lock (dirty_lock_);
10911128 dirty_snapshot = dirty_map_;
10921129 }
10931130
1131+ // 2. Now pin the reader count (read_map_ is stable)
1132+ rcu_reader_count_.fetch_add (1 , std::memory_order_acquire);
1133+
1134+ // RAII: release reader count on any exit path
1135+ struct RcuReaderGuard {
1136+ std::atomic<u64 > &cnt_;
1137+ ~RcuReaderGuard () { cnt_.fetch_sub (1 , std::memory_order_release); }
1138+ } guard{rcu_reader_count_};
1139+
1140+ InnerMap *current_read = read_map_;
1141+ auto read_begin = current_read->lower_bound (key_min);
1142+ auto read_end = current_read->upper_bound (key_max);
1143+
10941144 auto dirty_begin = dirty_snapshot->lower_bound (key_min);
10951145 auto dirty_end = dirty_snapshot->upper_bound (key_max);
10961146
@@ -1109,6 +1159,7 @@ void RcuMap<Key, Value>::Range(const Key &key_min, const Key &key_max, std::vect
11091159 items.emplace_back (it->first , it->second .value_ );
11101160 }
11111161 }
1162+ // ~RcuReaderGuard releases reader count
11121163}
11131164
11141165template <typename Key, typename Value>
0 commit comments