1#![expect(
2 unused_results,
3 reason = "The left-right writer API returns fluent handles and prior values that are intentionally not needed here."
4)]
5
6use hashbrown::HashMap;
15use left_right::{Absorb, ReadHandleFactory, WriteHandle};
16use std::hash::Hash;
17use std::sync::Mutex;
18
19enum MapOp<K, V> {
20 Insert(K, V),
21 Clear,
22}
23
24impl<K: Eq + Hash + Clone, V: Clone> Absorb<MapOp<K, V>> for HashMap<K, V> {
25 fn absorb_first(&mut self, operation: &mut MapOp<K, V>, _other: &Self) {
26 match operation {
27 MapOp::Insert(k, v) => {
28 self.insert(k.clone(), v.clone());
29 }
30 MapOp::Clear => self.clear(),
31 }
32 }
33
34 fn sync_with(&mut self, first: &Self) {
35 self.clone_from(first);
36 }
37}
38
39pub struct LrMap<K: Eq + Hash + Clone, V: Clone> {
46 reader_factory: ReadHandleFactory<HashMap<K, V>>,
47 writer: Mutex<WriteHandle<HashMap<K, V>, MapOp<K, V>>>,
48}
49
50impl<K, V> LrMap<K, V>
51where
52 K: Eq + Hash + Clone + Send + Sync,
53 V: Clone + Send + Sync,
54{
55 pub fn new() -> Self {
56 let (writer, reader) = left_right::new_from_empty(HashMap::new());
57 let factory = reader.factory();
58 Self {
59 reader_factory: factory,
60 writer: Mutex::new(writer),
61 }
62 }
63
64 pub fn get<Q>(&self, key: &Q) -> Option<V>
66 where
67 K: std::borrow::Borrow<Q>,
68 Q: Hash + Eq + ?Sized,
69 {
70 let reader = self.reader_factory.handle();
71 reader.enter().and_then(|map| map.get(key).cloned())
72 }
73
74 pub fn insert(&self, key: K, value: V) {
75 match self.writer.lock() {
76 Ok(mut w) => {
77 w.append(MapOp::Insert(key, value));
78 w.publish();
79 }
80 Err(e) => {
81 tracing::warn!("LrMap::insert failed due to poisoned mutex: {e}. Write dropped.");
82 }
83 }
84 }
85
86 pub fn clear(&self) {
87 match self.writer.lock() {
88 Ok(mut w) => {
89 w.append(MapOp::Clear);
90 w.publish();
91 }
92 Err(e) => {
93 tracing::warn!("LrMap::clear failed due to poisoned mutex: {e}. Operation dropped.");
94 }
95 }
96 }
97
98 pub fn len(&self) -> usize {
99 let reader = self.reader_factory.handle();
100 reader.enter().map(|m| m.len()).unwrap_or(0)
101 }
102
103 pub fn is_empty(&self) -> bool {
104 self.len() == 0
105 }
106}
107
108impl<K, V> Default for LrMap<K, V>
109where
110 K: Eq + Hash + Clone + Send + Sync,
111 V: Clone + Send + Sync,
112{
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use std::sync::Arc;
122
123 #[test]
124 fn insert_and_get() {
125 let map: LrMap<String, i32> = LrMap::new();
126 map.insert("a".into(), 1);
127 assert_eq!(map.get("a"), Some(1));
128 assert_eq!(map.get("b"), None);
129 }
130
131 #[test]
132 fn overwrite_key() {
133 let map: LrMap<String, i32> = LrMap::new();
134 map.insert("a".into(), 1);
135 map.insert("a".into(), 2);
136 assert_eq!(map.get("a"), Some(2));
137 }
138
139 #[test]
140 fn clear_removes_all() {
141 let map: LrMap<String, i32> = LrMap::new();
142 map.insert("a".into(), 1);
143 map.insert("b".into(), 2);
144 map.clear();
145 assert!(map.is_empty());
146 }
147
148 #[test]
149 fn concurrent_reads() {
150 let map: Arc<LrMap<String, i32>> = Arc::new(LrMap::new());
151 map.insert("key".into(), 42);
152
153 let handles: Vec<_> = (0..4)
154 .map(|_| {
155 let m = Arc::clone(&map);
156 std::thread::spawn(move || {
157 for _ in 0..100 {
158 assert_eq!(m.get("key"), Some(42));
159 }
160 })
161 })
162 .collect();
163
164 for h in handles {
165 h.join().expect("reader thread panicked");
166 }
167 }
168}