Skip to main content

xet_runtime/utils/
singleflight.rs

1//! A singleflight implementation for tokio.
2//!
3//! Inspired by [async_singleflight](https://crates.io/crates/async_singleflight).
4//!
5//! # Examples
6//!
7//! ```no_run
8//! use std::sync::Arc;
9//! use std::time::Duration;
10//!
11//! use futures::future::join_all;
12//! use xet_runtime::utils::singleflight::Group;
13//!
14//! const RES: usize = 7;
15//!
16//! async fn expensive_fn() -> Result<usize, ()> {
17//!     tokio::time::sleep(Duration::new(1, 500)).await;
18//!     Ok(RES)
19//! }
20//!
21//! #[tokio::main]
22//! async fn main() {
23//!     let g = Arc::new(Group::<_, ()>::new());
24//!     let mut handlers = Vec::new();
25//!     for _ in 0..10 {
26//!         let g = g.clone();
27//!         handlers.push(tokio::spawn(async move {
28//!             let res = g.work("key", expensive_fn()).await.0;
29//!             let r = res.unwrap();
30//!             println!("{}", r);
31//!         }));
32//!     }
33//!
34//!     join_all(handlers).await;
35//! }
36//! ```
37
38use std::collections::HashMap;
39use std::fmt::Debug;
40use std::future::Future;
41use std::marker::PhantomData;
42use std::pin::Pin;
43use std::sync::atomic::{AtomicBool, AtomicU16, Ordering};
44use std::sync::{Arc, Mutex, RwLock};
45use std::task::{Context, Poll, ready};
46
47use futures::future::Either;
48use pin_project::{pin_project, pinned_drop};
49use tokio::runtime::Handle;
50use tokio::sync::Notify;
51use tracing::{debug, error};
52
53pub use super::errors::SingleflightError;
54use crate::error_printer::ErrorPrinter;
55
56type SingleflightResult<T, E> = Result<T, SingleflightError<E>>;
57type CallMap<T, E> = HashMap<String, Arc<Call<T, E>>>;
58type CallCreate<'a, T, E> = (Arc<Call<T, E>>, CreateGuard<'a, T, E>);
59
60// Marker Traits to help make the code a bit cleaner.
61
62/// ResultType indicates the success type for a singleflight [Group].
63/// Since the actual processing might occur on a separate thread,
64/// we need to type to be [Send] + [Sync]. It also needs to be [Clone]
65/// so that we can clone the response across many tasks
66pub trait ResultType: Send + Clone + Sync + Debug {}
67impl<T: Send + Clone + Sync + Debug> ResultType for T {}
68
69/// Indicates the Error type for a singleflight [Group].
70/// The response might have been generated on a separate
71/// thread, thus, we need this type to be [Send] + [Sync].
72pub trait ResultError: Send + Debug + Sync {}
73impl<E: Send + Debug + Sync> ResultError for E {}
74
75/// Futures provided to a singleflight Group must produce a [Result<T, E>]
76/// for some T, E. This future must also be [Send]
77/// as it could be spawned as a tokio task.
78pub trait TaskFuture<T, E>: Future<Output = Result<T, E>> + Send {}
79impl<T, E, F: Future<Output = Result<T, E>> + Send> TaskFuture<T, E> for F {}
80
81/// Call represents the (eventual) results of running some Future.
82///
83/// It consists of a condition variable that can be waited upon until the
84/// owner task [completes](Call::complete) it.
85///
86/// Tasks can get the Call's result using [get_future](Call::get_future)
87/// to get a Future to await. Or they can call [get](Call::get)
88/// to try and get the result synchronously if the Call is already complete.
89#[derive(Debug, Clone)]
90struct Call<T, E>
91where
92    T: ResultType,
93    E: ResultError,
94{
95    // The condition variable
96    nt: Arc<Notify>,
97
98    // The result of the operation. Kept under a RWLock that is expected
99    // to be write-once, read-many.
100    // We use a lock instead of an AtomicPtr since updating the result and
101    // notifying the waiters needs to be atomic to avoid tasks missing the
102    // notification or to avoid tasks reading an empty value.
103    //
104    // Also important to note is that this lock is synchronous as we need
105    // to be able to store the value in the [OwnerTask::drop] function if
106    // the underlying future panics. Thus, complete() must be synchronous.
107    // This is ok since we are never holding the mutex across an await
108    // boundary (all functions are synchronous), and the critical section
109    // is fast.
110    res: Arc<RwLock<Option<SingleflightResult<T, E>>>>,
111
112    // Number of tasks that were waiting
113    num_waiters: Arc<AtomicU16>,
114}
115
116impl<T, E> Call<T, E>
117where
118    T: ResultType,
119    E: ResultError,
120{
121    fn new() -> Self {
122        Self {
123            nt: Arc::new(Notify::new()),
124            res: Arc::new(RwLock::new(None)),
125            num_waiters: Arc::new(AtomicU16::new(0)),
126        }
127    }
128
129    /// Completes the Call. This involves storing the provided result into the Call
130    /// and notifying all waiters that there is a value.
131    fn complete(&self, res: SingleflightResult<T, E>) {
132        // write-lock
133        let mut val = self.res.write().unwrap();
134        *val = Some(res);
135        self.nt.notify_waiters();
136        let num_waiters = self.num_waiters.load(Ordering::SeqCst);
137        debug!("Completed Call with: {} waiters", num_waiters);
138    }
139
140    /// Gets a Future that can be awaited to get the singleflight results, whenever that
141    /// might occur.
142    fn get_future(&self) -> impl Future<Output = SingleflightResult<T, E>> + '_ {
143        // read-lock
144        let res = self.res.read().unwrap();
145        if let Some(result) = res.clone() {
146            // we already have the result, provide it back to the caller.
147            debug!("Call already completed");
148            Either::Left(async move { result })
149        } else {
150            // no result yet, we are a waiter task.
151            self.num_waiters.fetch_add(1, Ordering::SeqCst);
152            debug!("Adding to Call's Notify");
153
154            // Note that the `notified()` needs to be performed outside the async
155            // block since we need to register our waiting within this read-lock
156            // or else, we might miss the owner task's notification.
157            let notified = self.nt.notified();
158            Either::Right(async move {
159                notified.await;
160                self.get()
161            })
162        }
163    }
164
165    /// Gets the result for the Call if set.
166    /// If not set, then [SingleflightError::NoResult] is returned
167    fn get(&self) -> SingleflightResult<T, E> {
168        let res = self.res.read().unwrap();
169        res.clone().unwrap_or(Err(SingleflightError::NoResult))
170    }
171}
172
173/// Group represents a class of work and creates a space in which units of work
174/// can be executed with duplicate suppression.
175#[derive(Debug)]
176pub struct Group<T, E>
177where
178    T: ResultType + 'static,
179    E: ResultError,
180{
181    call_map: Arc<Mutex<CallMap<T, E>>>,
182    _marker: PhantomData<fn(E)>,
183}
184
185impl<T, E: 'static> Default for Group<T, E>
186where
187    T: ResultType + 'static,
188    E: ResultError,
189{
190    fn default() -> Self {
191        Self {
192            call_map: Arc::new(Default::default()),
193            _marker: Default::default(),
194        }
195    }
196}
197
198impl<T, E: 'static> Group<T, E>
199where
200    T: ResultType + 'static,
201    E: ResultError,
202{
203    /// Create a new Group to do work with.
204    pub fn new() -> Group<T, E> {
205        Self::default()
206    }
207
208    /// Execute and return the value for a given function, making sure that only one
209    /// operation is in-flight at a given moment. If a duplicate call comes in, that caller will
210    /// wait until the original call completes and return the same value.
211    /// The second return value indicates whether the call is the owner.
212    ///
213    /// On error, the owner will receive the original error returned from the function
214    /// as a SingleflightError::InternalError, all waiters will receive a copy of the
215    /// error message wrapped in a SingleflightError::WaiterInternalError.
216    /// This is due to the fact that most error types don't implement Clone (e.g. anyhow::Error)
217    /// and thus we can't clone the original error for all the waiters.
218    pub async fn work(
219        &self,
220        key: &str,
221        fut: impl TaskFuture<T, E> + 'static,
222    ) -> (Result<T, SingleflightError<E>>, bool) {
223        // Get the call to use and a handle for retrieving the results
224        let (call, create_guard) = match self.get_call_or_create(key) {
225            Ok((call, create_guard)) => (call, create_guard),
226            Err(err) => return (Err(err), false),
227        };
228        // Use reference for created since we don't want it to drop until after this is done.
229        match &create_guard {
230            CreateGuard::Owned(_, _) => {
231                // spawn the owner task and wait
232                let owner_task = OwnerTask::new(fut, call.clone());
233                let owner_handle = Handle::current().spawn(owner_task);
234
235                // wait for the owner task to come back with results
236                match owner_handle.await {
237                    Ok(res) => (res, true),
238                    Err(e) => (Err(SingleflightError::JoinError(e.to_string())), true),
239                }
240            },
241            CreateGuard::Waiter => (call.get_future().await, false),
242        }
243    }
244
245    /// Like work but only returns the result, dumps the bool result value
246    pub async fn work_dump_caller_info(
247        &self,
248        key: &str,
249        fut: impl TaskFuture<T, E> + 'static,
250    ) -> Result<T, SingleflightError<E>> {
251        let (result, _) = self.work(key, fut).await;
252        result
253    }
254
255    /// Gets the [Call] to use from the call_map or else inserts a new Call
256    /// into the map.  
257    ///
258    /// Returns the [Call] that should be used and whether it was created or
259    /// not.
260    ///
261    /// Returns an error if the underlying `call_map` Mutex is poisoned.
262    fn get_call_or_create<'a>(&'a self, key: &'a str) -> Result<CallCreate<'a, T, E>, SingleflightError<E>> {
263        let mut m = self
264            .call_map
265            .lock()
266            .log_error("Failed to lock call map")
267            .map_err(|_| SingleflightError::GroupLockPoisoned)?;
268        if let Some(c) = m.get(key).cloned() {
269            Ok((c, CreateGuard::Waiter))
270        } else {
271            let c = Arc::new(Call::new());
272            let our_call = c.clone();
273            m.insert(key.to_owned(), c);
274            Ok((our_call, CreateGuard::Owned(self, key)))
275        }
276    }
277
278    /// Removes the [Call] associated with the Key. If there is no such [Call],
279    /// or the Group's `call_map` Mutex is poisoned, then an error is returned.
280    fn remove_call(&self, key: &str) -> SingleflightResult<(), E> {
281        let mut m = self
282            .call_map
283            .lock()
284            .log_error("Failed to lock call map")
285            .map_err(|_| SingleflightError::GroupLockPoisoned)?;
286        m.remove(key).ok_or(SingleflightError::CallMissing)?;
287        Ok(())
288    }
289}
290
291/// RAII for creating a Call in a Group. The guard indicates whether the Call is:
292/// - Owned - the current task owns the Call and will remove it from the Group's CallMap on [Self::drop]
293/// - Waiter - the current task is a waiter
294enum CreateGuard<'a, T, E>
295where
296    T: ResultType + 'static,
297    E: ResultError + 'static,
298{
299    Owned(&'a Group<T, E>, &'a str),
300    Waiter,
301}
302
303impl<T, E> Drop for CreateGuard<'_, T, E>
304where
305    T: ResultType + 'static,
306    E: ResultError + 'static,
307{
308    fn drop(&mut self) {
309        match self {
310            CreateGuard::Owned(group, key) => group
311                .remove_call(key)
312                .inspect_err(|err| error!(?err, "Couldn't remove call from map"))
313                .unwrap(),
314            CreateGuard::Waiter => {},
315        }
316    }
317}
318
319/// Defines a task to own the polling the Future and ensure the call is
320/// updated (i.e. result stored and waiters notified) when the Future
321/// completes (even if the future panics).
322///
323/// We can guarantee that the [Call] gets notified even during a Panic
324/// since tokio tasks will catch panics and call the `drop()` function.
325///
326/// For more info, see: https://github.com/tokio-rs/tokio/blob/4eed411519783ef6f58cbf74f886f91142b5cfa6/tokio/src/runtime/task/harness.rs#L453-L459
327/// and the discussion on: https://users.rust-lang.org/t/how-panic-calls-drop-functions/53663/8
328///
329/// Pin'ed since it is a Future implementation.
330#[pin_project(PinnedDrop)]
331#[must_use = "futures do nothing unless you `.await` or poll them"]
332struct OwnerTask<T, E, F>
333where
334    T: ResultType,
335    E: ResultError,
336    F: TaskFuture<T, E>,
337{
338    #[pin]
339    fut: F,
340    got_response: AtomicBool,
341    call: Arc<Call<T, E>>,
342}
343
344impl<T, E, F> OwnerTask<T, E, F>
345where
346    T: ResultType,
347    E: ResultError,
348    F: TaskFuture<T, E>,
349{
350    fn new(fut: F, call: Arc<Call<T, E>>) -> Self {
351        Self {
352            fut,
353            got_response: AtomicBool::new(false),
354            call,
355        }
356    }
357}
358
359impl<T, E, F> Future for OwnerTask<T, E, F>
360where
361    T: ResultType,
362    E: ResultError,
363    F: TaskFuture<T, E>,
364{
365    type Output = Result<T, SingleflightError<E>>;
366
367    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
368        let this = self.project();
369        let res: Result<T, E> = ready!(this.fut.poll(cx));
370        let res = res.map_err(|e| SingleflightError::InternalError(e));
371        // we have a result, so store it into our call and notify all waiters.
372        let call = this.call;
373        this.got_response.store(true, Ordering::SeqCst);
374        call.complete(res.clone());
375        Poll::Ready(res)
376    }
377}
378
379#[pinned_drop]
380impl<T, E, F> PinnedDrop for OwnerTask<T, E, F>
381where
382    T: ResultType,
383    E: ResultError,
384    F: TaskFuture<T, E>,
385{
386    fn drop(self: Pin<&mut Self>) {
387        // If we don't have a result stored in the call, then we panicked and
388        // should store an error, notifying all waiters of the panic.
389        let this = self.project();
390        if !this.got_response.load(Ordering::SeqCst) {
391            let call = this.call;
392            call.complete(Err(SingleflightError::OwnerPanicked));
393        }
394    }
395}
396
397#[cfg(test)]
398pub(crate) mod tests {
399    use std::sync::Arc;
400    use std::sync::atomic::{AtomicU32, Ordering};
401    use std::time::Duration;
402
403    use futures::future::join_all;
404    use tokio::runtime::Handle;
405    use tokio::task::JoinHandle;
406    use tokio::time::timeout;
407
408    use super::super::errors::SingleflightError;
409    use super::{Call, Group, OwnerTask};
410    use crate::core::XetContext;
411
412    /// A period of time for waiters to wait for a notification from the owner
413    /// task. This is expected to be sufficient time for the test futures to
414    /// complete. Thus, if we hit this timeout, then likely, there is something
415    /// wrong with the [Call] notifications.
416    pub(crate) const WAITER_TIMEOUT: Duration = Duration::from_millis(100);
417
418    const RES: usize = 7;
419
420    async fn return_res() -> Result<usize, ()> {
421        Ok(RES)
422    }
423
424    async fn expensive_fn(x: Arc<AtomicU32>, resp: usize) -> Result<usize, ()> {
425        tokio::time::sleep(Duration::new(1, 0)).await;
426        x.fetch_add(1, Ordering::SeqCst);
427        Ok(resp)
428    }
429
430    #[test]
431    fn test_simple_with_xet_runtime() {
432        let ctx = XetContext::default().unwrap();
433        let g = Group::new();
434        let res = ctx
435            .runtime
436            .bridge_sync(async move { g.work("key", return_res()).await })
437            .unwrap()
438            .0;
439        let r = res.unwrap();
440        assert_eq!(r, RES);
441    }
442
443    #[tokio::test]
444    async fn test_simple() {
445        let g = Group::new();
446        let res = g.work("key", return_res()).await.0;
447        let r = res.unwrap();
448        assert_eq!(r, RES);
449    }
450
451    #[test]
452    #[cfg_attr(feature = "smoke-test", ignore)]
453    fn test_multiple_threads_with_xet_runtime() {
454        let times_called = Arc::new(AtomicU32::new(0));
455        let ctx = XetContext::default().unwrap();
456        let g: Arc<Group<usize, ()>> = Arc::new(Group::new());
457        let mut handlers: Vec<JoinHandle<(usize, bool)>> = Vec::new();
458        let runtime = ctx.runtime.clone();
459        let tasks = async move {
460            for _ in 0..10 {
461                let g = g.clone();
462                let counter = times_called.clone();
463                handlers.push(runtime.spawn(async move {
464                    let tup = g.work("key", expensive_fn(counter, RES)).await;
465                    let res = tup.0;
466                    let fn_response = res.unwrap();
467                    (fn_response, tup.1)
468                }));
469            }
470
471            let num_callers = join_all(handlers)
472                .await
473                .into_iter()
474                .map(|r| r.unwrap())
475                .filter(|(val, is_caller)| {
476                    assert_eq!(*val, RES);
477                    *is_caller
478                })
479                .count();
480            assert_eq!(1, num_callers);
481            assert_eq!(1, times_called.load(Ordering::SeqCst));
482        };
483        ctx.runtime.bridge_sync(tasks).unwrap();
484    }
485
486    #[tokio::test]
487    #[cfg_attr(feature = "smoke-test", ignore)]
488    async fn test_multiple_threads() {
489        let times_called = Arc::new(AtomicU32::new(0));
490        let g: Arc<Group<usize, ()>> = Arc::new(Group::new());
491        let mut handlers: Vec<JoinHandle<(usize, bool)>> = Vec::new();
492        for _ in 0..10 {
493            let g = g.clone();
494            let counter = times_called.clone();
495            handlers.push(Handle::current().spawn(async move {
496                let tup = g.work("key", expensive_fn(counter, RES)).await;
497                let res = tup.0;
498                let fn_response = res.unwrap();
499                (fn_response, tup.1)
500            }));
501        }
502
503        let num_callers = join_all(handlers)
504            .await
505            .into_iter()
506            .map(|r| r.unwrap())
507            .filter(|(val, is_caller)| {
508                assert_eq!(*val, RES);
509                *is_caller
510            })
511            .count();
512        assert_eq!(1, num_callers);
513        assert_eq!(1, times_called.load(Ordering::SeqCst));
514    }
515
516    #[tokio::test]
517    #[cfg_attr(feature = "smoke-test", ignore)]
518    async fn test_error() {
519        let times_called = Arc::new(AtomicU32::new(0));
520
521        async fn expensive_error_fn(x: Arc<AtomicU32>) -> Result<usize, &'static str> {
522            tokio::time::sleep(Duration::new(1, 500)).await;
523            x.fetch_add(1, Ordering::SeqCst);
524            Err("Error")
525        }
526
527        let g: Arc<Group<usize, &'static str>> = Arc::new(Group::new());
528        let mut handlers = Vec::new();
529
530        for _ in 0..10 {
531            let g = g.clone();
532            let counter = times_called.clone();
533            handlers.push(Handle::current().spawn(async move {
534                let tup = g.work("key", expensive_error_fn(counter)).await;
535                let res = tup.0;
536                assert!(res.is_err());
537                tup.1
538            }));
539        }
540
541        let num_callers = join_all(handlers).await.into_iter().map(|r| r.unwrap()).filter(|b| *b).count();
542        assert_eq!(1, num_callers);
543        assert_eq!(1, times_called.load(Ordering::SeqCst));
544    }
545
546    #[tokio::test]
547    #[cfg_attr(feature = "smoke-test", ignore)]
548    async fn test_multiple_keys() {
549        let times_called_x = Arc::new(AtomicU32::new(0));
550        let times_called_y = Arc::new(AtomicU32::new(0));
551
552        let mut handlers1 = call_success_n_times(5, "key", times_called_x.clone(), 7);
553        let mut handlers2 = call_success_n_times(5, "key2", times_called_y.clone(), 13);
554        handlers1.append(&mut handlers2);
555        let count_x = AtomicU32::new(0);
556        let count_y = AtomicU32::new(0);
557
558        let num_callers = join_all(handlers1)
559            .await
560            .into_iter()
561            .map(|r| r.unwrap())
562            .filter(|(val, is_caller)| {
563                if *val == 7 {
564                    count_x.fetch_add(1, Ordering::SeqCst);
565                } else if *val == 13 {
566                    count_y.fetch_add(1, Ordering::SeqCst);
567                } else {
568                    panic!("joined a number not expected: {}", *val);
569                }
570                *is_caller
571            })
572            .count();
573        assert_eq!(2, num_callers);
574        assert_eq!(5, count_x.load(Ordering::SeqCst));
575        assert_eq!(5, count_y.load(Ordering::SeqCst));
576        assert_eq!(1, times_called_x.load(Ordering::SeqCst));
577        assert_eq!(1, times_called_y.load(Ordering::SeqCst));
578    }
579
580    // must be run in a #[tokio::test]
581    fn call_success_n_times(times: usize, key: &str, c: Arc<AtomicU32>, val: usize) -> Vec<JoinHandle<(usize, bool)>> {
582        let g: Arc<Group<usize, ()>> = Arc::new(Group::new());
583        let mut handlers = Vec::new();
584        for _ in 0..times {
585            let g = g.clone();
586            let counter = c.clone();
587            let k = key.to_owned();
588            handlers.push(Handle::current().spawn(async move {
589                let tup = g.work(k.as_str(), expensive_fn(counter, val)).await;
590                let res = tup.0;
591                let fn_response = res.unwrap();
592                (fn_response, tup.1)
593            }));
594        }
595        handlers
596    }
597
598    #[tokio::test]
599    async fn test_owner_task_future_impl() {
600        const VAL: i32 = 10;
601        let future = async { Ok::<i32, String>(VAL) };
602        let call = Arc::new(Call::new());
603        let owner_task = OwnerTask::new(future, call.clone());
604        let result = tokio::spawn(owner_task).await;
605        assert_eq!(VAL, result.unwrap().unwrap());
606        assert_eq!(VAL, call.get().unwrap());
607    }
608
609    #[tokio::test]
610    async fn test_owner_task_future_notify() {
611        const VAL: i32 = 10;
612        let future = async { Ok::<i32, String>(VAL) };
613        let call = Arc::new(Call::new());
614        let call_waiter = call.clone();
615        let waiter_task = async move {
616            let waiter_future = call_waiter.get_future();
617            assert_eq!(VAL, waiter_future.await.unwrap());
618        };
619        let waiter_handle = tokio::spawn(waiter_task);
620        let owner_task = OwnerTask::new(future, call.clone());
621        let result = tokio::spawn(owner_task).await;
622        timeout(WAITER_TIMEOUT, waiter_handle).await.unwrap().unwrap();
623        assert_eq!(VAL, result.unwrap().unwrap());
624        assert_eq!(VAL, call.get().unwrap());
625        assert_eq!(1, call.num_waiters.load(Ordering::SeqCst)) // we should have had 1 waiter
626    }
627
628    #[tokio::test]
629    async fn test_owner_task_future_panic() {
630        let future = async { panic!("failing task") };
631        let call = Arc::new(Call::<i32, String>::new());
632        let call_waiter = call.clone();
633        let waiter_task = async move {
634            let waiter_future = call_waiter.get_future();
635            let result = waiter_future.await;
636            assert!(matches!(result, Err(SingleflightError::OwnerPanicked)));
637        };
638        let waiter_handle = tokio::spawn(waiter_task);
639
640        let owner_task = OwnerTask::new(future, call.clone());
641        let result = tokio::spawn(owner_task).await;
642        assert!(result.is_err());
643        timeout(WAITER_TIMEOUT, waiter_handle).await.unwrap().unwrap();
644        assert_eq!(1, call.num_waiters.load(Ordering::SeqCst)) // we should have had 1 waiter
645    }
646}
647
648#[cfg(test)]
649mod test_deadlock {
650    use std::collections::HashMap;
651    use std::sync::Arc;
652
653    use futures::StreamExt;
654    use futures::stream::iter;
655    use tests::WAITER_TIMEOUT;
656    use tokio::runtime::Handle;
657    use tokio::sync::mpsc::error::SendError;
658    use tokio::sync::mpsc::{Sender, channel};
659    use tokio::sync::{Mutex, Notify};
660    use tokio::time::timeout;
661
662    use super::{Group, tests};
663
664    #[tokio::test]
665    async fn test_deadlock() {
666        /*
667        Each spawned tokio task is expected to send some ints to the main task via a bounded buffer.
668        The ints are fetched using a futures::Buffered stream over some future. These futures will
669        call into singleflight to fetch an int.
670
671        To set up the deadlock, we have 3 tasks: main, t1, and t2 with the following dependency:
672        main is waiting to read from t1, t1 is a waiter on some element that t2 is working on,
673        t2 is blocked writing to the buffer (i.e. waiting for main to read).
674
675        to accomplish this, we spawn t1, t2. Each will start up their sub-tasks (3 at a time).
676        However, there is a dependency where task2[2] runs for some int x and task1[4] needs
677        that value, thus triggering a dependency within singleflight.
678         */
679
680        let group: Arc<Group<usize, ()>> = Arc::new(Group::new());
681        // communication channels
682        let (send1, mut recv1) = channel::<usize>(1);
683        let (send2, mut recv2) = channel::<usize>(1);
684        // Items to return on the channels from the tasks.
685        let vals1: Vec<usize> = vec![1, 2, 3, 4, SHARED_ITEM];
686        let vals2: Vec<usize> = vec![6, 7, SHARED_ITEM, 8, 9];
687
688        // waiters allows us to define the order that sub-tasks run in the underlying tasks.
689        // We need this for 2 reasons:
690        // 1. SHARED_ITEM sub-task in t2 needs to block until we can ensure that it has a waiter
691        // 2. vals2[1] needs to block to ensure that t2's SHARED_ITEM starts.
692        let waiters: Arc<Mutex<HashMap<usize, Arc<Notify>>>> = Arc::new(Mutex::new(HashMap::new()));
693        {
694            let mut guard = waiters.lock().await;
695            guard.insert(vals2[1], Arc::new(Notify::new()));
696            guard.insert(SHARED_ITEM, Arc::new(Notify::new()));
697        }
698
699        // spawn tasks
700        let t1 = Handle::current().spawn(run_task(1, group.clone(), waiters.clone(), send1, false, vals1.clone()));
701        let t2 = Handle::current().spawn(run_task(2, group.clone(), waiters.clone(), send2, true, vals2.clone()));
702
703        // try to receive all the values from task1 without getting stuck.
704        for (i, expected_val) in vals1.into_iter().enumerate() {
705            if i == 3 {
706                // resume vals2[1] to allow task2 to get "stuck" waiting on send2.send()
707                println!("[main] notifying val: {}", vals2[1]);
708                let guard = waiters.lock().await;
709                guard.get(&vals2[1]).unwrap().notify_one();
710                println!("[main] notified val: {}", vals2[1])
711            }
712            if i == 4 {
713                // resume task2's SHARED_ITEM sub-task since we now have a waiter (i.e. vals1[4]).
714                println!("[main] notifying val: {}", SHARED_ITEM);
715                let guard = waiters.lock().await;
716                guard.get(&SHARED_ITEM).unwrap().notify_one();
717                println!("[main] notified val: {}", SHARED_ITEM);
718            }
719            println!("[main] getting t1[{}]", i);
720            let res = timeout(WAITER_TIMEOUT, recv1.recv())
721                .await
722                .map_err(|_| format!("Timed out on task1 waiting for val: {}. Likely deadlock.", expected_val));
723            let val = res.unwrap().unwrap();
724            println!("[main] got val: {} from t1[{}]", val, i);
725            assert_eq!(expected_val, val);
726        }
727
728        // try to receive all the values from task2 without getting stuck.
729        for expected_val in vals2 {
730            let res = timeout(WAITER_TIMEOUT, recv2.recv())
731                .await
732                .map_err(|_| format!("Timed out on task2 waiting for val: {}. Likely deadlock.", expected_val));
733            let val = res.unwrap().unwrap();
734            assert_eq!(expected_val, val);
735        }
736
737        // make sure t1,t2 completed successfully.
738        t1.await.unwrap().unwrap();
739        t2.await.unwrap().unwrap();
740    }
741
742    const SHARED_ITEM: usize = 5;
743
744    async fn run_task(
745        id: i32,
746        g: Arc<Group<usize, ()>>,
747        waiters: Arc<Mutex<HashMap<usize, Arc<Notify>>>>,
748        send_chan: Sender<usize>,
749        should_own: bool,
750        vals: Vec<usize>,
751    ) -> Result<(), SendError<usize>> {
752        // create a buffered stream that will run at most 3 sub-tasks concurrently.
753        let mut strm = iter(vals.into_iter().map(|v| {
754            let g = g.clone();
755            let waiters = waiters.clone();
756            // get the sub-task for the given item.
757            async move {
758                println!("[task: {}] running task for: {}", id, v);
759                let (res, is_owner) = g.work(format!("{}", v).as_str(), run_fut(v, waiters)).await;
760                println!("[task: {}] completed task for: {}, is_owner: {}", id, v, is_owner);
761                if v == SHARED_ITEM {
762                    assert_eq!(should_own, is_owner);
763                }
764                res.unwrap()
765            }
766        }))
767        .buffered(3);
768
769        while let Some(val) = strm.next().await {
770            println!("[task: {}] sending next element: {}", id, val);
771            send_chan.send(val).await?;
772            println!("[task: {}] sent next element: {}", id, val);
773        }
774        println!("[task: {}] done executing", id);
775        Ok(())
776    }
777
778    async fn run_fut(v: usize, waiters: Arc<Mutex<HashMap<usize, Arc<Notify>>>>) -> Result<usize, ()> {
779        let waiter = {
780            let x = waiters.lock().await;
781            x.get(&v).cloned()
782        };
783        // wait for the main task to tell us to proceed.
784        if let Some(waiter) = waiter {
785            println!("val: {}, waiting for signal", v);
786            waiter.notified().await;
787            println!("val: {}, woke up from signal", v);
788        }
789        Ok(v)
790    }
791}
792
793#[cfg(test)]
794mod test_futures_unordered {
795    use std::future::Future;
796    use std::pin::Pin;
797    use std::sync::Arc;
798    use std::time::Duration;
799
800    use futures_util::TryStreamExt;
801    use futures_util::stream::FuturesUnordered;
802    use tokio::sync::mpsc;
803    use tokio::time::sleep;
804
805    use super::super::errors::SingleflightError;
806    use super::Group;
807
808    type FutType = Pin<Box<dyn Future<Output = Result<(i32, bool), SingleflightError<String>>> + Send>>;
809
810    #[tokio::test]
811    async fn test_dropped_owner() {
812        /*
813         We test out a situation where the owner of a task is dropped before the task can complete.
814         This is done by having the owner task be part of a FuturesUnordered execution where a
815         separate task errors-out, cancelling the others.
816
817         We expect that when an owning task is dropped, that the spawned owning task is still able
818         to complete in the background, that the Call state is properly cleaned up, and that a
819         new `work()` invocation for the key runs as an owner.
820
821             main       fut_error     fut_owner     owner_task    fut_waiter
822         try_collect()====>|------------->|              |
823              |        start(k2)      start(k1)------>start()
824              |<----------err             |              |         start(k1)
825             err----------------------->drop()           |             |
826              |                                        Ok(1)-------->Ok(1)
827        */
828        let group = Arc::new(Group::new());
829
830        // ready channels help the owner task tell the waiter task to start
831        let (ready_tx, mut ready_rx) = mpsc::channel(1);
832        // done channels help the owner task signal to main that the operation completed,
833        // even though fut_owner was dropped.
834        let (done_tx, mut done_rx) = mpsc::channel(1);
835
836        // Owner task for "key1" that will delay then return a `1`.
837        let fut_owner = get_fut(group.clone(), "key1", async move {
838            ready_tx.send(true).await.unwrap();
839            sleep(Duration::from_millis(100)).await;
840            done_tx.send(true).await.unwrap();
841            Ok(1)
842        });
843        // Waiter task for "key1" that should not get called (uses the results of owner task)
844        let fut_waiter =
845            get_fut(group.clone(), "key1", async { Err("Test BUG: waiter should not be called".to_string()) });
846
847        // Task for "key2" that will fail and cause fut_owner to be dropped.
848        let fut_err = get_fut(group.clone(), "key2", async { Err("failed".to_string()) });
849
850        // spawn a task to wait for fut_owner to be ready then run fut_waiter
851        let handle = tokio::spawn(async move {
852            assert!(ready_rx.recv().await.unwrap());
853            let (i, is_owner) = fut_waiter.await.unwrap();
854            assert!(!is_owner);
855            assert_eq!(i, 1);
856        });
857
858        // Use FuturesUnordered to run `fut_owner` and `fut_error`. Since `fut_error` immediately fails,
859        // it will complete first, causing the try_collect to short-circuit and drop `fut_owner`
860        //
861        // Implementation note: the order of the vec matters since FuturesUnordered will try to
862        // run the futures in-order (until it hits an await). If `fut_err` is first, since it has
863        // no awaits, it will immediately finish (i.e. err), causing fut_owner to never get run.
864        let futures: Result<Vec<(i32, bool)>, SingleflightError<String>> =
865            FuturesUnordered::from_iter(vec![fut_owner, fut_err]).try_collect().await;
866
867        assert!(futures.is_err());
868        // "key1" should be deleted from the call_map even though fut_owner was dropped before finishing
869        assert!(!group.call_map.lock().unwrap().contains_key("key1"));
870        assert!(done_rx.recv().await.unwrap());
871        handle.await.unwrap();
872
873        // Ensure that subsequent calls to the same key are able to go through as there are
874        // no currently running tasks.
875        let fut_after = get_fut(group, "key1", async { Ok(5) });
876        let (i, is_owner) = fut_after.await.unwrap();
877        assert!(is_owner);
878        assert_eq!(i, 5);
879    }
880
881    fn get_fut(
882        g: Arc<Group<i32, String>>,
883        key: &str,
884        f: impl Future<Output = Result<i32, String>> + Send + 'static,
885    ) -> FutType {
886        let key = key.to_string();
887        Box::pin(async move {
888            let (res, is_owner) = g.work(&key, f).await;
889            let i = res?;
890            Ok((i, is_owner))
891        })
892    }
893}