1// Take a look at the license at the top of the repository in the LICENSE file.
23use std::num::NonZeroU64;
45use futures_channel::oneshot;
6use futures_core::Future;
7use glib::{prelude::*, translate::*};
89use crate::{ffi, Cancellable};
1011// 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);
1718impl CancelledHandlerId {
19// rustdoc-stripper-ignore-next
20/// Returns the internal signal handler ID.
21#[allow(clippy::missing_safety_doc)]
22pub unsafe fn as_raw(&self) -> libc::c_ulong {
23self.0.get() as libc::c_ulong
24 }
25}
2627impl TryFromGlib<libc::c_ulong> for CancelledHandlerId {
28type Error = GlibNoneError;
29#[inline]
30unsafe fn try_from_glib(val: libc::c_ulong) -> Result<Self, GlibNoneError> {
31NonZeroU64::new(valas _).map(Self).ok_or(GlibNoneError)
32 }
33}
3435mod sealed {
36pub trait Sealed {}
37impl<T: super::IsA<super::Cancellable>> Sealedfor T {}
38}
3940pub 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")]
60fn connect_cancelled<F: FnOnce(&Self) + Send + 'static>(
61&self,
62 callback: F,
63 ) -> Option<CancelledHandlerId> {
64unsafe extern "C" fn connect_trampoline<P: IsA<Cancellable>, F: FnOnce(&P)>(
65 this: *mut ffi::GCancellable,
66 callback: glib::ffi::gpointer,
67 ) {
68let callback: &mut Option<F> = &mut *(callbackas *mut Option<F>);
69let callback = callback70 .take()
71 .expect("Cancellable::cancel() closure called multiple times");
72callback(Cancellable::from_glib_borrow(this).unsafe_cast_ref())
73 }
7475unsafe extern "C" fn destroy_closure<F>(ptr: glib::ffi::gpointer) {
76let _ = Box::<Option<F>>::from_raw(ptras *mut _);
77 }
7879let callback: Box<Option<F>> = Box::new(Some(callback));
80unsafe {
81from_glib(ffi::g_cancellable_connect(
82self.as_ptr() as *mut _,
83Some(std::mem::transmute::<*const (), unsafe extern "C" fn()>(
84connect_trampoline::<Self, F> as *const (),
85 )),
86Box::into_raw(callback) as *mut _,
87Some(destroy_closure::<F>),
88 ))
89 }
90 }
91// rustdoc-stripper-ignore-next
92/// Local variant of [`Self::connect_cancelled`].
93#[doc(alias = "g_cancellable_connect")]
94fn connect_cancelled_local<F: FnOnce(&Self) + 'static>(
95&self,
96 callback: F,
97 ) -> Option<CancelledHandlerId> {
98let callback = glib::thread_guard::ThreadGuard::new(callback);
99100self.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")]
111fn disconnect_cancelled(&self, id: CancelledHandlerId) {
112unsafe { 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.
117fn future(&self) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>> {
118let cancellable = self.as_ref().clone();
119let (tx, rx) = oneshot::channel();
120let id = cancellable.connect_cancelled(move |_| {
121let _ = tx.send(());
122 });
123Box::pin(async move {
124rx.await.unwrap();
125if let Some(id) = id {
126cancellable.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")]
140fn set_error_if_cancelled(&self) -> Result<(), glib::Error> {
141unsafe {
142let mut error = std::ptr::null_mut();
143let is_ok = ffi::g_cancellable_set_error_if_cancelled(
144self.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.
149debug_assert_eq!(is_ok == glib::ffi::GFALSE, error.is_null());
150if error.is_null() {
151Ok(())
152 } else {
153Err(from_glib_full(error))
154 }
155 }
156 }
157}
158159impl<O: IsA<Cancellable>> CancellableExtManualfor O {}
160161#[cfg(test)]
162mod tests {
163use super::*;
164165use crate::prelude::*;
166167#[test]
168fn cancellable_callback() {
169let c = Cancellable::new();
170let id = c.connect_cancelled(|_| {});
171 c.cancel(); // if it doesn't crash at this point, then we're good to go!
172c.disconnect_cancelled(id.unwrap());
173 }
174175#[test]
176fn cancellable_callback_local() {
177let c = Cancellable::new();
178let id = c.connect_cancelled_local(|_| {});
179 c.cancel(); // if it doesn't crash at this point, then we're good to go!
180c.disconnect_cancelled(id.unwrap());
181 }
182183#[test]
184fn cancellable_error_if_cancelled() {
185let c = Cancellable::new();
186 c.cancel();
187assert!(c.set_error_if_cancelled().is_err());
188 }
189190#[test]
191fn cancellable_future() {
192let c = Cancellable::new();
193 c.cancel();
194 glib::MainContext::new().block_on(c.future());
195 }
196197#[test]
198fn cancellable_future_thread() {
199let cancellable = Cancellable::new();
200let c = cancellable.clone();
201 std::thread::spawn(move || c.cancel()).join().unwrap();
202 glib::MainContext::new().block_on(cancellable.future());
203 }
204205#[test]
206fn cancellable_future_delayed() {
207let ctx = glib::MainContext::new();
208let c = Cancellable::new();
209let (tx, rx) = oneshot::channel();
210 {
211let 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}