1use std::{
24 collections::HashMap,
25 future::Future,
26 hash::Hash,
27 pin::Pin,
28 sync::{
29 Arc, Weak,
30 atomic::{AtomicU64, Ordering},
31 },
32 task::{Context, Poll},
33 time::Duration,
34};
35
36use parking_lot::Mutex;
37use tokio::{
38 sync::oneshot,
39 time::{Sleep, sleep},
40};
41
42mod error;
43
44pub use error::Error;
45use error::Result;
46
47struct Entry<R> {
51 id: u64,
52 sender: oneshot::Sender<R>,
53}
54
55struct Inner<K: Eq + Hash, R> {
59 timeout_ms: AtomicU64,
62 next_id: AtomicU64,
63 requests: Mutex<HashMap<K, Entry<R>>>,
64}
65
66pub struct ResponseWaiter<K: Eq + Hash, R> {
73 receiver: oneshot::Receiver<R>,
74 sleep: Pin<Box<Sleep>>,
75 inner: Weak<Inner<K, R>>,
76 key: K,
77 id: u64,
78 done: bool,
80}
81
82impl<K: Eq + Hash, R> Unpin for ResponseWaiter<K, R> {}
86
87impl<K: Eq + Hash, R> ResponseWaiter<K, R> {
88 fn clear(&mut self) {
91 if let Some(inner) = self.inner.upgrade() {
92 let mut guard = inner.requests.lock();
93 if guard.get(&self.key).is_some_and(|e| e.id == self.id) {
94 guard.remove(&self.key);
95 }
96 }
97 }
98}
99
100impl<K: Eq + Hash, R> Future for ResponseWaiter<K, R> {
101 type Output = Result<R>;
102
103 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
104 let this = self.get_mut();
107
108 match Pin::new(&mut this.receiver).poll(cx) {
110 Poll::Ready(Ok(response)) => {
111 this.done = true;
112 return Poll::Ready(Ok(response));
113 }
114 Poll::Ready(Err(_)) => {
115 this.done = true;
117 return Poll::Ready(Err(Error::Canceled));
118 }
119 Poll::Pending => {}
120 }
121
122 if this.sleep.as_mut().poll(cx).is_ready() {
123 this.clear();
124 this.done = true;
125 return Poll::Ready(Err(Error::RequestTimeout));
126 }
127
128 Poll::Pending
129 }
130}
131
132impl<K: Eq + Hash, R> Drop for ResponseWaiter<K, R> {
133 fn drop(&mut self) {
134 if !self.done {
135 self.clear();
136 }
137 }
138}
139
140pub struct PendingRequests<K: Eq + Hash, R> {
149 inner: Arc<Inner<K, R>>,
150}
151
152impl<K: Eq + Hash, R> Clone for PendingRequests<K, R> {
153 fn clone(&self) -> Self {
154 Self {
155 inner: Arc::clone(&self.inner),
156 }
157 }
158}
159
160const DEFAULT_TIMEOUT_MS: u64 = 6000;
161
162impl<K: Eq + Hash, R> Default for PendingRequests<K, R> {
163 fn default() -> Self {
164 Self {
165 inner: Arc::new(Inner {
166 timeout_ms: AtomicU64::new(DEFAULT_TIMEOUT_MS),
167 next_id: AtomicU64::new(0),
168 requests: Mutex::new(HashMap::new()),
169 }),
170 }
171 }
172}
173
174impl<K: Eq + Hash + Clone, R> PendingRequests<K, R> {
175 pub fn new() -> Self {
177 Self::default()
178 }
179
180 pub fn set_timeout(&self, timeout: Duration) {
184 self.inner
185 .timeout_ms
186 .store(timeout.as_millis() as u64, Ordering::Relaxed);
187 }
188
189 pub fn timeout(&self) -> Duration {
191 Duration::from_millis(self.inner.timeout_ms.load(Ordering::Relaxed))
192 }
193
194 pub fn len(&self) -> usize {
196 self.inner.requests.lock().len()
197 }
198
199 pub fn is_empty(&self) -> bool {
201 self.inner.requests.lock().is_empty()
202 }
203
204 pub fn contains(&self, key: &K) -> bool {
206 self.inner.requests.lock().contains_key(key)
207 }
208
209 pub fn prepare_response(&self, key: K) -> Result<ResponseWaiter<K, R>> {
214 self.prepare_response_with_timeout(key, self.timeout())
215 }
216
217 pub fn prepare_response_with_timeout(
219 &self,
220 key: K,
221 timeout: Duration,
222 ) -> Result<ResponseWaiter<K, R>> {
223 let mut guard = self.inner.requests.lock();
224 if guard.contains_key(&key) {
225 return Err(Error::KeyAlreadyExists);
226 }
227
228 let id = self.inner.next_id.fetch_add(1, Ordering::Relaxed);
229 let (tx, rx) = oneshot::channel();
230 guard.insert(key.clone(), Entry { id, sender: tx });
231
232 Ok(ResponseWaiter {
233 receiver: rx,
234 sleep: Box::pin(sleep(timeout)),
235 inner: Arc::downgrade(&self.inner),
236 key,
237 id,
238 done: false,
239 })
240 }
241
242 pub fn handle_response(&self, key: K, response: R) -> Result<()> {
247 let entry = self
248 .inner
249 .requests
250 .lock()
251 .remove(&key)
252 .ok_or(Error::KeyNotFound)?;
253
254 entry
255 .sender
256 .send(response)
257 .map_err(|_| Error::ReceiverDropped)
258 }
259
260 pub fn cancel(&self, key: &K) -> Result<()> {
265 self.inner
266 .requests
267 .lock()
268 .remove(key)
269 .map(|_| ())
270 .ok_or(Error::KeyNotFound)
271 }
272
273 pub fn cancel_all(&self) -> usize {
279 let mut guard = self.inner.requests.lock();
280 let n = guard.len();
281 guard.clear();
282 n
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[tokio::test]
291 async fn roundtrip_delivers_response() {
292 let requests = PendingRequests::<u64, &str>::new();
293 let waiter = requests.prepare_response(1).unwrap();
294
295 requests.handle_response(1, "pong").unwrap();
296
297 assert_eq!(waiter.await.unwrap(), "pong");
298 assert!(requests.is_empty());
299 }
300
301 #[tokio::test]
302 async fn duplicate_key_is_rejected() {
303 let requests = PendingRequests::<u64, ()>::new();
304 let _waiter = requests.prepare_response(1).unwrap();
305
306 assert!(matches!(
307 requests.prepare_response(1),
308 Err(Error::KeyAlreadyExists)
309 ));
310 }
311
312 #[tokio::test]
313 async fn unknown_key_response_errors() {
314 let requests = PendingRequests::<u64, ()>::new();
315 assert!(matches!(
316 requests.handle_response(42, ()),
317 Err(Error::KeyNotFound)
318 ));
319 }
320
321 #[tokio::test(start_paused = true)]
322 async fn timeout_resolves_and_cleans_up() {
323 let requests = PendingRequests::<u64, ()>::new();
324 let waiter = requests
325 .prepare_response_with_timeout(1, Duration::from_millis(50))
326 .unwrap();
327
328 assert!(matches!(waiter.await, Err(Error::RequestTimeout)));
329 assert!(requests.is_empty(), "timed-out entry should be removed");
330 }
331
332 #[tokio::test]
333 async fn cancel_resolves_waiter() {
334 let requests = PendingRequests::<u64, ()>::new();
335 let waiter = requests.prepare_response(1).unwrap();
336
337 requests.cancel(&1).unwrap();
338
339 assert!(matches!(waiter.await, Err(Error::Canceled)));
340 assert!(matches!(requests.cancel(&1), Err(Error::KeyNotFound)));
341 }
342
343 #[tokio::test]
344 async fn dropped_waiter_is_reported_and_cleaned() {
345 let requests = PendingRequests::<u64, ()>::new();
346 let waiter = requests.prepare_response(1).unwrap();
347
348 drop(waiter);
349
350 assert!(
351 requests.is_empty(),
352 "dropped waiter should remove its entry"
353 );
354 assert!(matches!(
355 requests.handle_response(1, ()),
356 Err(Error::KeyNotFound)
357 ));
358 }
359
360 #[tokio::test]
361 async fn dropping_registry_cancels_waiter() {
362 let requests = PendingRequests::<u64, ()>::new();
363 let waiter = requests.prepare_response(1).unwrap();
364
365 drop(requests);
366
367 assert!(matches!(waiter.await, Err(Error::Canceled)));
368 }
369
370 #[tokio::test(start_paused = true)]
371 async fn reused_key_after_timeout_is_independent() {
372 let requests = PendingRequests::<u64, &str>::new();
373 let first = requests
374 .prepare_response_with_timeout(1, Duration::from_millis(50))
375 .unwrap();
376 assert!(matches!(first.await, Err(Error::RequestTimeout)));
377
378 let second = requests.prepare_response(1).unwrap();
380 requests.handle_response(1, "ok").unwrap();
381 assert_eq!(second.await.unwrap(), "ok");
382 }
383
384 #[tokio::test]
385 async fn clones_share_state() {
386 let requests = PendingRequests::<u64, &str>::new();
387 let other = requests.clone();
388
389 let waiter = requests.prepare_response(1).unwrap();
391 other.handle_response(1, "pong").unwrap();
392
393 assert_eq!(waiter.await.unwrap(), "pong");
394 assert!(other.is_empty());
395 }
396
397 #[tokio::test]
398 async fn contains_reflects_pending_state() {
399 let requests = PendingRequests::<u64, ()>::new();
400 assert!(!requests.contains(&1));
401
402 let _waiter = requests.prepare_response(1).unwrap();
403 assert!(requests.contains(&1));
404
405 requests.cancel(&1).unwrap();
406 assert!(!requests.contains(&1));
407 }
408
409 #[tokio::test]
410 async fn cancel_all_fails_every_waiter() {
411 let requests = PendingRequests::<u64, ()>::new();
412 let a = requests.prepare_response(1).unwrap();
413 let b = requests.prepare_response(2).unwrap();
414
415 assert_eq!(requests.cancel_all(), 2);
416 assert!(requests.is_empty());
417 assert!(matches!(a.await, Err(Error::Canceled)));
418 assert!(matches!(b.await, Err(Error::Canceled)));
419 }
420
421 #[tokio::test]
422 async fn set_timeout_works_through_shared_handle() {
423 let requests = PendingRequests::<u64, ()>::new();
424 let handle = requests.clone();
425
426 handle.set_timeout(Duration::from_secs(30));
427 assert_eq!(requests.timeout(), Duration::from_secs(30));
428 }
429
430 #[tokio::test]
431 async fn surviving_clone_keeps_waiter_alive() {
432 let requests = PendingRequests::<u64, &str>::new();
433 let clone = requests.clone();
434 let waiter = requests.prepare_response(1).unwrap();
435
436 drop(requests);
438 clone.handle_response(1, "still here").unwrap();
439 assert_eq!(waiter.await.unwrap(), "still here");
440 }
441}