Skip to main content

common_batcher/
worker_registry.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::hash::Hash;
16
17use dashmap::DashMap;
18use dashmap::mapref::entry::Entry;
19use tokio::sync::mpsc::{self, Receiver, Sender};
20
21/// Registers worker senders by key, without owning workers or their execution.
22///
23/// Closed senders are replaced on lookup/creation. Cleanup checks channel
24/// identity under the same lock as replacement, so an old worker cannot remove
25/// a replacement registered under its key.
26pub 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    /// Returns a live sender, if one is currently registered.
44    /// The receiver can close after this method returns; callers must handle a
45    /// failed send and retry worker lookup without discarding the unsent item.
46    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    /// Returns the registered sender and, only when created, its receiver.
54    ///
55    /// Start the new worker before the next await so cancellation cannot leave
56    /// a registered channel without a consumer. Existing channels retain their
57    /// original capacity. New channel capacity must satisfy `mpsc::channel`.
58    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    /// Reuses a live sender or atomically creates its replacement.
71    ///
72    /// `create` runs synchronously under the registry shard lock. It should only
73    /// prepare the sender and capture any initialization state (such as the
74    /// receiver) for the caller; start the worker after this method returns.
75    /// Do not block or reenter the registry from `create`.
76    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    /// Removes the key only if it still points to this worker's channel.
96    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    /// Number of registered entries, including senders whose receivers closed.
103    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        // Release the read guard before joining, even if lookup needs a write lock.
134        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        // Closed entries remain accounted until replacement or explicit removal.
176        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                // Keep the newly created receiver alive while both lookups run.
209                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}