common_batcher/
worker_registry.rs1use std::hash::Hash;
16
17use dashmap::DashMap;
18use dashmap::mapref::entry::Entry;
19use tokio::sync::mpsc::{self, Receiver, Sender};
20
21pub struct WorkerRegistry<K, T> {
27 workers: DashMap<K, Sender<T>>,
28}
29
30impl<K: Eq + Hash, T> Default for WorkerRegistry<K, T> {
31 fn default() -> Self {
32 Self {
33 workers: DashMap::new(),
34 }
35 }
36}
37
38impl<K: Eq + Hash, T> WorkerRegistry<K, T> {
39 pub fn new() -> Self {
40 Self::default()
41 }
42
43 pub async fn get(&self, key: &K) -> Option<Sender<T>> {
47 self.workers
48 .get(key)
49 .filter(|tx| !tx.is_closed())
50 .map(|tx| tx.value().clone())
51 }
52
53 pub async fn get_or_create(&self, key: K, capacity: usize) -> (Sender<T>, Option<Receiver<T>>) {
59 let mut receiver = None;
60 let sender = self
61 .get_or_insert_with(key, || {
62 let (sender, rx) = mpsc::channel(capacity);
63 receiver = Some(rx);
64 sender
65 })
66 .await;
67 (sender, receiver)
68 }
69
70 pub async fn get_or_insert_with<F>(&self, key: K, create: F) -> Sender<T>
77 where
78 F: FnOnce() -> Sender<T>,
79 {
80 if let Some(tx) = self.get(&key).await {
81 return tx;
82 }
83
84 match self.workers.entry(key) {
85 Entry::Occupied(mut entry) => {
86 if entry.get().is_closed() {
87 entry.insert(create());
88 }
89 entry.get().clone()
90 }
91 Entry::Vacant(entry) => entry.insert(create()).value().clone(),
92 }
93 }
94
95 pub async fn remove_if_same(&self, key: &K, tx: &Sender<T>) -> bool {
97 self.workers
98 .remove_if(key, |_, current| current.same_channel(tx))
99 .is_some()
100 }
101
102 pub async fn len(&self) -> usize {
104 self.workers.len()
105 }
106
107 pub async fn is_empty(&self) -> bool {
108 self.workers.is_empty()
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use std::sync::Arc;
115 use std::sync::atomic::{AtomicUsize, Ordering};
116
117 use tokio::sync::{Barrier, mpsc};
118
119 use crate::worker_registry::WorkerRegistry;
120
121 #[tokio::test]
122 async fn test_live_lookup_uses_read_lock() {
123 let registry = Arc::new(WorkerRegistry::<_, ()>::new());
124 let (sender, _receiver) = registry.get_or_create(1, 1).await;
125 let guard = registry.workers.get(&1).unwrap();
126 let (result_tx, result_rx) = std::sync::mpsc::channel();
127 let worker_registry = registry.clone();
128 let runtime = tokio::runtime::Handle::current();
129 let thread = std::thread::spawn(move || {
130 let result = runtime.block_on(worker_registry.get_or_create(1, 1));
131 let _ = result_tx.send(result);
132 });
133 let result = result_rx.recv_timeout(std::time::Duration::from_secs(5));
135 drop(guard);
136 thread.join().unwrap();
137 let (reused, receiver) = result.expect("live lookup must not wait for an exclusive lock");
138 assert!(sender.same_channel(&reused));
139 assert!(receiver.is_none());
140 }
141
142 #[tokio::test]
143 async fn test_reuses_live_sender_and_returns_receiver_to_caller() {
144 let registry = WorkerRegistry::new();
145 assert!(registry.is_empty().await);
146 assert!(registry.get(&1).await.is_none());
147 let mut receiver = None;
148 let first = registry
149 .get_or_insert_with(1, || {
150 let (tx, rx) = mpsc::channel(1);
151 receiver = Some(rx);
152 tx
153 })
154 .await;
155 let second = registry
156 .get_or_insert_with(1, || panic!("live worker must be reused"))
157 .await;
158 assert!(first.same_channel(&second));
159 assert!(registry.get(&1).await.unwrap().same_channel(&first));
160 first.send(7).await.unwrap();
161 assert_eq!(Some(7), receiver.as_mut().unwrap().recv().await);
162 assert_eq!(1, registry.len().await);
163 assert!(registry.remove_if_same(&1, &first).await);
164 assert!(!registry.remove_if_same(&1, &first).await);
165 assert!(registry.is_empty().await);
166 }
167
168 #[tokio::test]
169 async fn test_old_cleanup_does_not_remove_replacement() {
170 let registry = WorkerRegistry::<_, ()>::new();
171 let (first, mut receiver) = mpsc::channel(1);
172 registry.get_or_insert_with("table", || first.clone()).await;
173 receiver.close();
174 assert!(registry.get(&"table").await.is_none());
175 assert_eq!(1, registry.len().await);
177 let (second, _receiver) = mpsc::channel(1);
178 registry
179 .get_or_insert_with("table", || second.clone())
180 .await;
181 assert!(!registry.remove_if_same(&"table", &first).await);
182 assert!(registry.get(&"table").await.unwrap().same_channel(&second));
183 assert_eq!(1, registry.len().await);
184 assert!(registry.remove_if_same(&"table", &second).await);
185 }
186
187 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
188 async fn test_concurrent_lookup_initializes_once() {
189 let registry = Arc::new(WorkerRegistry::<_, ()>::new());
190 let barrier = Arc::new(Barrier::new(3));
191 let count = Arc::new(AtomicUsize::new(0));
192 let mut tasks = Vec::new();
193 for _ in 0..2 {
194 let registry = registry.clone();
195 let barrier = barrier.clone();
196 let count = count.clone();
197 tasks.push(tokio::spawn(async move {
198 barrier.wait().await;
199 let mut receiver = None;
200 let tx = registry
201 .get_or_insert_with(1, || {
202 count.fetch_add(1, Ordering::Relaxed);
203 let (tx, rx) = mpsc::channel(1);
204 receiver = Some(rx);
205 tx
206 })
207 .await;
208 barrier.wait().await;
210 (tx, receiver)
211 }));
212 }
213 barrier.wait().await;
214 barrier.wait().await;
215 let first = tasks.remove(0).await.unwrap();
216 let second = tasks.remove(0).await.unwrap();
217 assert!(first.0.same_channel(&second.0));
218 assert_eq!(1, count.load(Ordering::Relaxed));
219 assert_eq!(1, registry.len().await);
220 }
221 #[tokio::test]
222 async fn test_channel_creation_replacement_and_cleanup() {
223 let registry = WorkerRegistry::<_, usize>::new();
224 let (first, receiver) = registry.get_or_create(1, 2).await;
225 let mut receiver = receiver.unwrap();
226 let (reused, absent) = registry.get_or_create(1, 3).await;
227 assert!(absent.is_none());
228 assert!(first.same_channel(&reused));
229 assert_eq!(2, reused.max_capacity());
230 first.send(7).await.unwrap();
231 assert_eq!(Some(7), receiver.recv().await);
232 receiver.close();
233 let (replacement, receiver) = registry.get_or_create(1, 3).await;
234 assert!(receiver.is_some());
235 assert_eq!(3, replacement.max_capacity());
236 assert!(!first.same_channel(&replacement));
237 assert!(!registry.remove_if_same(&1, &first).await);
238 assert!(registry.get(&1).await.unwrap().same_channel(&replacement));
239 assert!(registry.remove_if_same(&1, &replacement).await);
240 }
241
242 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
243 async fn test_concurrent_channel_creation_returns_one_receiver() {
244 let registry = Arc::new(WorkerRegistry::<_, ()>::new());
245 let barrier = Arc::new(Barrier::new(3));
246 let mut tasks = Vec::new();
247 for _ in 0..2 {
248 let registry = registry.clone();
249 let barrier = barrier.clone();
250 tasks.push(tokio::spawn(async move {
251 barrier.wait().await;
252 let channel = registry.get_or_create(1, 2).await;
253 barrier.wait().await;
254 channel
255 }));
256 }
257 barrier.wait().await;
258 barrier.wait().await;
259 let first = tasks.remove(0).await.unwrap();
260 let second = tasks.remove(0).await.unwrap();
261 assert!(first.0.same_channel(&second.0));
262 assert_ne!(first.1.is_some(), second.1.is_some());
263 assert_eq!(1, registry.len().await);
264 }
265}