Skip to main content

atuin_common/sync/
eager_future_cell.rs

1use std::future::Future;
2use std::sync::Arc;
3
4use parking_lot::Mutex;
5use tokio::runtime::Handle;
6use tokio::sync::{Notify, OnceCell};
7use tokio::task::AbortHandle;
8
9/// A cell whose value is seeded with a task scheduled at [`EagerFutureCell::new`], in the
10/// background.
11///
12/// [`EagerFuture::new`] accepts a future which is executed in the background. [`EagerFuture::get`]
13/// either waits for the future to produce the value, or returns the already-stored value.
14pub type EagerFutureCell<T> = EagerFuture<OnceCell<T>>;
15
16/// A cell whose value is seeded with a task scheduled at [`MutEagerFutureCell::new`], in the
17/// background. Unlike the [`EagerFutureCell`] one-shot dual, [`MutEagerFutureCell`] allows you to
18/// emplace any arbitrary value into the cell, via the `overwrite` call.
19pub type MutEagerFutureCell<T> = EagerFuture<Mutex<Option<T>>>;
20
21impl<T: Clone + Send + Sync + 'static> MutEagerFutureCell<T> {
22    /// Force `value` into the cell, aborting the background future so its (now-superseded) result is
23    /// discarded. Any current or future [`get`](EagerFuture::get) observes `value`.
24    pub fn overwrite(&self, value: T) {
25        self.abort.abort();
26        *self.inner.cell.lock() = Some(value);
27        self.inner.ready.notify_waiters();
28    }
29}
30
31/// Acts as a storage backend to [`EagerFutureCell`].
32pub trait ResultCell: Default + Send + Sync + 'static {
33    type Value: Clone + Send + Sync + 'static;
34
35    /// Place the value into the cell.
36    fn fill(&self, value: Self::Value);
37
38    /// Read the value from the cell.
39    fn peek(&self) -> Option<Self::Value>;
40}
41
42impl<T: Clone + Send + Sync + 'static> ResultCell for OnceCell<T> {
43    type Value = T;
44
45    fn fill(&self, value: T) {
46        let _ = self.set(value);
47    }
48
49    fn peek(&self) -> Option<T> {
50        self.get().cloned()
51    }
52}
53
54impl<T: Clone + Send + Sync + 'static> ResultCell for Mutex<Option<T>> {
55    type Value = T;
56
57    fn fill(&self, value: T) {
58        let mut slot = self.lock();
59        // Keep an existing value: an `overwrite` may have already won.
60        if slot.is_none() {
61            *slot = Some(value);
62        }
63    }
64
65    fn peek(&self) -> Option<T> {
66        self.lock().clone()
67    }
68}
69
70/// Data stored under the [`EagerFuture`].
71#[derive(Debug)]
72struct Inner<C> {
73    cell: C,
74    ready: Notify,
75}
76
77/// A cell whose value is seeded with a task scheduled at [`EagerFutureCell::new`], in the
78/// background.
79///
80/// Use [`EagerFutureCell`] or [`MutEagerFutureCell`], directly.
81pub struct EagerFuture<C> {
82    inner: Arc<Inner<C>>,
83    abort: AbortHandle,
84}
85
86impl<C> Clone for EagerFuture<C> {
87    fn clone(&self) -> Self {
88        Self {
89            inner: Arc::clone(&self.inner),
90            abort: self.abort.clone(),
91        }
92    }
93}
94
95impl<C> std::fmt::Debug for EagerFuture<C> {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.debug_struct("EagerFuture").finish_non_exhaustive()
98    }
99}
100
101impl<C: ResultCell> EagerFuture<C> {
102    /// Initialize the cell with the given `work` future, starting the work immediately on `handle`.
103    pub fn new<Fut>(work: Fut, handle: &Handle) -> Self
104    where
105        Fut: Future<Output = C::Value> + Send + 'static,
106    {
107        let inner = Arc::new(Inner {
108            cell: C::default(),
109            ready: Notify::new(),
110        });
111
112        // Drive on a spawned task, so a `get` cancelled while waiting cannot strand the work.
113        let driver = Arc::clone(&inner);
114        let task = handle.spawn(async move {
115            let value = work.await;
116            driver.cell.fill(value);
117            driver.ready.notify_waiters();
118        });
119
120        Self {
121            inner,
122            abort: task.abort_handle(),
123        }
124    }
125
126    /// Fetch a clone of the value, awaiting the work if necessary.
127    pub async fn get(&self) -> C::Value {
128        if let Some(value) = self.inner.cell.peek() {
129            return value;
130        }
131
132        loop {
133            let notified = self.inner.ready.notified();
134            tokio::pin!(notified);
135            notified.as_mut().enable();
136
137            if let Some(value) = self.inner.cell.peek() {
138                return value;
139            }
140
141            notified.await;
142        }
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use std::sync::Arc;
149    use std::sync::atomic::{AtomicUsize, Ordering};
150    use std::time::Duration;
151
152    use rstest::rstest;
153
154    use super::{EagerFutureCell, MutEagerFutureCell};
155
156    #[rstest]
157    #[case(42)]
158    #[case(7)]
159    #[tokio::test]
160    async fn computes_once_and_caches(#[case] value: usize) {
161        let calls = Arc::new(AtomicUsize::new(0));
162        let counter = calls.clone();
163        let cell: EagerFutureCell<usize> = EagerFutureCell::new(
164            async move {
165                counter.fetch_add(1, Ordering::SeqCst);
166                value
167            },
168            &tokio::runtime::Handle::current(),
169        );
170
171        // Repeated gets return the cached value, and the work runs exactly once even though the
172        // eager background kick and these gets can race.
173        assert_eq!(cell.get().await, value);
174        assert_eq!(cell.get().await, value);
175        assert_eq!(calls.load(Ordering::SeqCst), 1);
176    }
177
178    #[rstest]
179    fn constructs_from_a_handle_outside_the_runtime() {
180        // The explicit handle lets us construct (and eagerly spawn) from a thread that is not
181        // itself running inside the runtime.
182        let calls = Arc::new(AtomicUsize::new(0));
183        let counter = calls.clone();
184        let rt = tokio::runtime::Runtime::new().unwrap();
185        let cell: EagerFutureCell<usize> = EagerFutureCell::new(
186            async move {
187                counter.fetch_add(1, Ordering::SeqCst);
188                7usize
189            },
190            rt.handle(),
191        );
192
193        assert_eq!(rt.block_on(cell.get()), 7);
194        assert_eq!(calls.load(Ordering::SeqCst), 1);
195    }
196
197    #[rstest]
198    #[tokio::test]
199    async fn overwrite_supersedes_a_slow_future() {
200        let ran = Arc::new(AtomicUsize::new(0));
201        let counter = ran.clone();
202        let cell: MutEagerFutureCell<usize> = MutEagerFutureCell::new(
203            async move {
204                // Long enough that the `overwrite` below wins the race.
205                tokio::time::sleep(Duration::from_secs(30)).await;
206                counter.fetch_add(1, Ordering::SeqCst);
207                1
208            },
209            &tokio::runtime::Handle::current(),
210        );
211
212        cell.overwrite(2);
213
214        // The emplaced value is observed, and the aborted future never ran to completion.
215        assert_eq!(cell.get().await, 2);
216        assert_eq!(ran.load(Ordering::SeqCst), 0);
217    }
218
219    #[rstest]
220    #[tokio::test]
221    async fn overwrite_replaces_an_already_completed_future() {
222        let cell: MutEagerFutureCell<usize> =
223            MutEagerFutureCell::new(async move { 1 }, &tokio::runtime::Handle::current());
224
225        // Let the eager future resolve first, then overwrite it.
226        assert_eq!(cell.get().await, 1);
227        cell.overwrite(2);
228        assert_eq!(cell.get().await, 2);
229    }
230}