Skip to main content

gio/
cancellable_future.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{
4    fmt::{Debug, Display},
5    future::Future,
6    pin::Pin,
7    sync::Arc,
8    task::{Context, Poll},
9};
10
11use futures_util::task::AtomicWaker;
12use pin_project_lite::pin_project;
13
14use crate::{Cancellable, CancelledHandlerId, IOErrorEnum, prelude::*};
15
16// rustdoc-stripper-ignore-next
17/// Indicator that the [`CancellableFuture`] was cancelled.
18pub struct Cancelled;
19
20pin_project! {
21    // rustdoc-stripper-ignore-next
22    /// A future which can be cancelled via [`Cancellable`].
23    ///
24    /// # Examples
25    ///
26    /// ```
27    /// # use futures::FutureExt;
28    /// # use gio::prelude::*;
29    /// # use gio::CancellableFuture;
30    /// let l = glib::MainLoop::new(None, false);
31    /// let c = gio::Cancellable::new();
32    ///
33    /// l.context().spawn_local(CancellableFuture::new(async { 42 }, c.clone()).map(|_| ()));
34    /// c.cancel();
35    ///
36    /// ```
37    pub struct CancellableFuture<F> {
38        #[pin]
39        future: F,
40
41        waker: Arc<AtomicWaker>,
42
43        waker_handler_cb: Option<CancelledHandlerId>,
44
45        cancellable: Cancellable,
46    }
47
48    impl<F> PinnedDrop for CancellableFuture<F> {
49        fn drop(this: Pin<&mut Self>) {
50            let this = this.project();
51
52            if let Some(handler) = this.waker_handler_cb.take() {
53                this.cancellable.disconnect_cancelled(handler);
54            }
55        }
56    }
57}
58
59impl<F> CancellableFuture<F> {
60    // rustdoc-stripper-ignore-next
61    /// Creates a new `CancellableFuture` using a [`Cancellable`].
62    ///
63    /// When [`cancel`](CancellableExt::cancel) is called, the future will complete
64    /// immediately without making any further progress. In such a case, an error
65    /// will be returned by this future (i.e., [`Cancelled`]).
66    pub fn new(future: F, cancellable: Cancellable) -> Self {
67        Self {
68            future,
69            waker: Arc::default(),
70            waker_handler_cb: None,
71            cancellable,
72        }
73    }
74
75    // rustdoc-stripper-ignore-next
76    /// Checks whether the future has been cancelled.
77    ///
78    /// This is a shortcut for `self.cancellable().is_cancelled()`
79    ///
80    /// Note that all this method indicates is whether [`cancel`](CancellableExt::cancel)
81    /// was called. This means that it will return true even if:
82    ///   * `cancel` was called after the future had completed.
83    ///   * `cancel` was called while the future was being polled.
84    #[inline]
85    pub fn is_cancelled(&self) -> bool {
86        self.cancellable.is_cancelled()
87    }
88
89    // rustdoc-stripper-ignore-next
90    /// Returns the inner [`Cancellable`] associated during creation.
91    #[inline]
92    pub fn cancellable(&self) -> &Cancellable {
93        &self.cancellable
94    }
95}
96
97impl<F> Future for CancellableFuture<F>
98where
99    F: Future,
100{
101    type Output = Result<<F as Future>::Output, Cancelled>;
102
103    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
104        if self.is_cancelled() {
105            return Poll::Ready(Err(Cancelled));
106        }
107
108        let this = self.project();
109
110        this.waker.register(cx.waker());
111
112        match this.future.poll(cx) {
113            Poll::Ready(out) => {
114                if let Some(handler) = this.waker_handler_cb.take() {
115                    this.cancellable.disconnect_cancelled(handler);
116                }
117
118                this.waker.take();
119
120                Poll::Ready(Ok(out))
121            }
122
123            Poll::Pending => {
124                if this.waker_handler_cb.is_none() {
125                    let waker = Arc::clone(this.waker);
126
127                    match this.cancellable.connect_cancelled(move |_| waker.wake()) {
128                        Some(handler) => *this.waker_handler_cb = Some(handler),
129
130                        None => return Poll::Ready(Err(Cancelled)),
131                    }
132                }
133
134                Poll::Pending
135            }
136        }
137    }
138}
139
140impl From<Cancelled> for glib::Error {
141    fn from(_: Cancelled) -> Self {
142        glib::Error::new(IOErrorEnum::Cancelled, "Task cancelled")
143    }
144}
145
146impl std::error::Error for Cancelled {}
147
148impl Debug for Cancelled {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        write!(f, "Task cancelled")
151    }
152}
153
154impl Display for Cancelled {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        Debug::fmt(self, f)
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use std::{
163        sync::Arc,
164        task::{Wake, Waker},
165    };
166
167    use futures_channel::oneshot;
168
169    use super::{Cancellable, CancellableFuture, Cancelled, Context, Future, Poll};
170    use crate::prelude::*;
171
172    #[test]
173    fn cancellable_future_ok() {
174        let ctx = glib::MainContext::new();
175        let c = Cancellable::new();
176        let (tx, rx) = oneshot::channel();
177
178        {
179            ctx.spawn_local(async {
180                let cancellable_future = CancellableFuture::new(async { 42 }, c);
181                assert!(!cancellable_future.is_cancelled());
182
183                let result = cancellable_future.await;
184                assert!(matches!(result, Ok(42)));
185
186                tx.send(()).unwrap();
187            });
188        }
189
190        ctx.block_on(rx).unwrap()
191    }
192
193    #[test]
194    fn cancellable_future_cancel() {
195        let ctx = glib::MainContext::new();
196        let c = Cancellable::new();
197        let (tx, rx) = oneshot::channel();
198
199        {
200            let c = c.clone();
201            ctx.spawn_local(async move {
202                let cancellable_future = CancellableFuture::new(std::future::pending::<()>(), c);
203
204                let result = cancellable_future.await;
205                assert!(matches!(result, Err(Cancelled)));
206
207                tx.send(()).unwrap();
208            });
209        }
210
211        std::thread::spawn(move || c.cancel()).join().unwrap();
212
213        ctx.block_on(rx).unwrap();
214    }
215
216    #[test]
217    fn cancellable_future_releases_waker_on_drop() {
218        let noop_wake = Arc::new(utils::NoopWake);
219        let waker = Waker::from(Arc::clone(&noop_wake));
220        let mut cx = Context::from_waker(&waker);
221
222        let cancellable = Cancellable::new();
223
224        let mut cancellable_future = Box::pin(CancellableFuture::new(
225            std::future::pending::<()>(),
226            cancellable.clone(),
227        ));
228
229        assert_eq!(Arc::strong_count(&noop_wake), 2);
230        let _ = Future::poll(cancellable_future.as_mut(), &mut cx);
231        assert_eq!(Arc::strong_count(&noop_wake), 3);
232
233        drop(cancellable_future);
234        assert_eq!(Arc::strong_count(&noop_wake), 2);
235    }
236
237    #[test]
238    fn cancellable_future_releases_waker_on_ready() {
239        let noop_wake = Arc::new(utils::NoopWake);
240        let waker = Waker::from(Arc::clone(&noop_wake));
241        let mut cx = Context::from_waker(&waker);
242
243        let cancellable = Cancellable::new();
244
245        let mut cancellable_future = Box::pin(CancellableFuture::new(
246            std::future::poll_fn({
247                let mut pending = true;
248
249                move |_| {
250                    if std::mem::take(&mut pending) {
251                        Poll::Pending
252                    } else {
253                        Poll::Ready(())
254                    }
255                }
256            }),
257            cancellable.clone(),
258        ));
259
260        assert_eq!(Arc::strong_count(&noop_wake), 2);
261
262        assert!(Future::poll(cancellable_future.as_mut(), &mut cx).is_pending());
263
264        assert_eq!(Arc::strong_count(&noop_wake), 3);
265
266        assert!(Future::poll(cancellable_future.as_mut(), &mut cx).is_ready());
267
268        assert_eq!(Arc::strong_count(&noop_wake), 2);
269    }
270
271    mod utils {
272        use super::*;
273
274        pub struct NoopWake;
275
276        // We need a manual noop waker, because we need to put it in an `Arc`.
277        #[allow(clippy::manual_noop_waker)]
278        impl Wake for NoopWake {
279            fn wake(self: Arc<Self>) {}
280        }
281    }
282}