gio/
cancellable.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::num::NonZeroU64;
4
5use futures_channel::oneshot;
6use futures_core::Future;
7use glib::{prelude::*, translate::*};
8
9use crate::{ffi, Cancellable};
10
11// rustdoc-stripper-ignore-next
12/// The id of a cancelled handler that is returned by `CancellableExtManual::connect`. This type is
13/// analogous to [`glib::SignalHandlerId`].
14#[derive(Debug, Eq, PartialEq)]
15#[repr(transparent)]
16pub struct CancelledHandlerId(NonZeroU64);
17
18impl CancelledHandlerId {
19    // rustdoc-stripper-ignore-next
20    /// Returns the internal signal handler ID.
21    #[allow(clippy::missing_safety_doc)]
22    pub unsafe fn as_raw(&self) -> libc::c_ulong {
23        self.0.get() as libc::c_ulong
24    }
25}
26
27impl TryFromGlib<libc::c_ulong> for CancelledHandlerId {
28    type Error = GlibNoneError;
29    #[inline]
30    unsafe fn try_from_glib(val: libc::c_ulong) -> Result<Self, GlibNoneError> {
31        NonZeroU64::new(val as _).map(Self).ok_or(GlibNoneError)
32    }
33}
34
35mod sealed {
36    pub trait Sealed {}
37    impl<T: super::IsA<super::Cancellable>> Sealed for T {}
38}
39
40pub trait CancellableExtManual: sealed::Sealed + IsA<Cancellable> {
41    // rustdoc-stripper-ignore-next
42    /// Convenience function to connect to the `signal::Cancellable::cancelled` signal. Also
43    /// handles the race condition that may happen if the cancellable is cancelled right before
44    /// connecting. If the operation is cancelled from another thread, `callback` will be called
45    /// in the thread that cancelled the operation, not the thread that is running the operation.
46    /// This may be the main thread, so the callback should not do something that can block.
47    ///
48    /// `callback` is called at most once, either directly at the time of the connect if `self` is
49    /// already cancelled, or when `self` is cancelled in some thread.
50    ///
51    /// Since GLib 2.40, the lock protecting `self` is not held when `callback` is invoked. This
52    /// lifts a restriction in place for earlier GLib versions which now makes it easier to write
53    /// cleanup code that unconditionally invokes e.g.
54    /// [`CancellableExt::cancel()`][crate::prelude::CancellableExt::cancel()].
55    ///
56    /// # Returns
57    ///
58    /// The id of the signal handler or `None` if `self` has already been cancelled.
59    #[doc(alias = "g_cancellable_connect")]
60    fn connect_cancelled<F: FnOnce(&Self) + Send + 'static>(
61        &self,
62        callback: F,
63    ) -> Option<CancelledHandlerId> {
64        unsafe extern "C" fn connect_trampoline<P: IsA<Cancellable>, F: FnOnce(&P)>(
65            this: *mut ffi::GCancellable,
66            callback: glib::ffi::gpointer,
67        ) {
68            let callback: &mut Option<F> = &mut *(callback as *mut Option<F>);
69            let callback = callback
70                .take()
71                .expect("Cancellable::cancel() closure called multiple times");
72            callback(Cancellable::from_glib_borrow(this).unsafe_cast_ref())
73        }
74
75        unsafe extern "C" fn destroy_closure<F>(ptr: glib::ffi::gpointer) {
76            let _ = Box::<Option<F>>::from_raw(ptr as *mut _);
77        }
78
79        let callback: Box<Option<F>> = Box::new(Some(callback));
80        unsafe {
81            from_glib(ffi::g_cancellable_connect(
82                self.as_ptr() as *mut _,
83                Some(std::mem::transmute::<*const (), unsafe extern "C" fn()>(
84                    connect_trampoline::<Self, F> as *const (),
85                )),
86                Box::into_raw(callback) as *mut _,
87                Some(destroy_closure::<F>),
88            ))
89        }
90    }
91    // rustdoc-stripper-ignore-next
92    /// Local variant of [`Self::connect_cancelled`].
93    #[doc(alias = "g_cancellable_connect")]
94    fn connect_cancelled_local<F: FnOnce(&Self) + 'static>(
95        &self,
96        callback: F,
97    ) -> Option<CancelledHandlerId> {
98        let callback = glib::thread_guard::ThreadGuard::new(callback);
99
100        self.connect_cancelled(move |obj| (callback.into_inner())(obj))
101    }
102    // rustdoc-stripper-ignore-next
103    /// Disconnects a handler from a cancellable instance. Additionally, in the event that a signal
104    /// handler is currently running, this call will block until the handler has finished. Calling
105    /// this function from a callback registered with [`Self::connect_cancelled`] will therefore
106    /// result in a deadlock.
107    ///
108    /// This avoids a race condition where a thread cancels at the same time as the cancellable
109    /// operation is finished and the signal handler is removed.
110    #[doc(alias = "g_cancellable_disconnect")]
111    fn disconnect_cancelled(&self, id: CancelledHandlerId) {
112        unsafe { ffi::g_cancellable_disconnect(self.as_ptr() as *mut _, id.as_raw()) };
113    }
114    // rustdoc-stripper-ignore-next
115    /// Returns a `Future` that completes when the cancellable becomes cancelled. Completes
116    /// immediately if the cancellable is already cancelled.
117    fn future(&self) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>> {
118        let cancellable = self.as_ref().clone();
119        let (tx, rx) = oneshot::channel();
120        let id = cancellable.connect_cancelled(move |_| {
121            let _ = tx.send(());
122        });
123        Box::pin(async move {
124            rx.await.unwrap();
125            if let Some(id) = id {
126                cancellable.disconnect_cancelled(id);
127            }
128        })
129    }
130    // rustdoc-stripper-ignore-next
131    /// Set an error if the cancellable is already cancelled.
132    // rustdoc-stripper-ignore-next-stop
133    /// If the @self is cancelled, sets the error to notify
134    /// that the operation was cancelled.
135    ///
136    /// # Returns
137    ///
138    /// [`true`] if @self was cancelled, [`false`] if it was not
139    #[doc(alias = "g_cancellable_set_error_if_cancelled")]
140    fn set_error_if_cancelled(&self) -> Result<(), glib::Error> {
141        unsafe {
142            let mut error = std::ptr::null_mut();
143            let is_ok = ffi::g_cancellable_set_error_if_cancelled(
144                self.as_ref().to_glib_none().0,
145                &mut error,
146            );
147            // Here's the special case, this function has an inverted
148            // return value for the error case.
149            debug_assert_eq!(is_ok == glib::ffi::GFALSE, error.is_null());
150            if error.is_null() {
151                Ok(())
152            } else {
153                Err(from_glib_full(error))
154            }
155        }
156    }
157}
158
159impl<O: IsA<Cancellable>> CancellableExtManual for O {}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    use crate::prelude::*;
166
167    #[test]
168    fn cancellable_callback() {
169        let c = Cancellable::new();
170        let id = c.connect_cancelled(|_| {});
171        c.cancel(); // if it doesn't crash at this point, then we're good to go!
172        c.disconnect_cancelled(id.unwrap());
173    }
174
175    #[test]
176    fn cancellable_callback_local() {
177        let c = Cancellable::new();
178        let id = c.connect_cancelled_local(|_| {});
179        c.cancel(); // if it doesn't crash at this point, then we're good to go!
180        c.disconnect_cancelled(id.unwrap());
181    }
182
183    #[test]
184    fn cancellable_error_if_cancelled() {
185        let c = Cancellable::new();
186        c.cancel();
187        assert!(c.set_error_if_cancelled().is_err());
188    }
189
190    #[test]
191    fn cancellable_future() {
192        let c = Cancellable::new();
193        c.cancel();
194        glib::MainContext::new().block_on(c.future());
195    }
196
197    #[test]
198    fn cancellable_future_thread() {
199        let cancellable = Cancellable::new();
200        let c = cancellable.clone();
201        std::thread::spawn(move || c.cancel()).join().unwrap();
202        glib::MainContext::new().block_on(cancellable.future());
203    }
204
205    #[test]
206    fn cancellable_future_delayed() {
207        let ctx = glib::MainContext::new();
208        let c = Cancellable::new();
209        let (tx, rx) = oneshot::channel();
210        {
211            let c = c.clone();
212            ctx.spawn_local(async move {
213                c.future().await;
214                tx.send(()).unwrap();
215            });
216        }
217        std::thread::spawn(move || c.cancel()).join().unwrap();
218        ctx.block_on(rx).unwrap();
219    }
220}