Skip to content

Commit 95a43a2

Browse files
authored
Merge pull request #589 from jf2048/cancellable-extras
More gio::Cancellable methods
2 parents 3bbca97 + 0de4a0f commit 95a43a2

File tree

5 files changed

+177
-55
lines changed

5 files changed

+177
-55
lines changed

gio/Gir.toml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,13 +328,26 @@ generate_builder = true
328328
name = "Gio.Cancellable"
329329
status = "generate"
330330
concurrency = "send+sync"
331+
manual_traits = ["CancellableExtManual"]
332+
[[object.function]]
333+
name = "connect"
334+
# Needs manual bindings for closures
335+
ignore = true
336+
[[object.function]]
337+
name = "disconnect"
338+
# Needs manual bindings for CancelledHandlerId
339+
ignore = true
331340
[[object.function]]
332341
name = "reset"
333342
#undefined behaviour
334343
ignore = true
335344
[[object.function]]
336345
name = "source_new"
337-
# Needs manual bindings for setting callback, etc
346+
# Only for use with other souce implementations
347+
ignore = true
348+
[[object.signal]]
349+
name = "cancelled"
350+
# Racy, use 'connect' and 'disconnect' instead
338351
ignore = true
339352

340353
[[object]]

gio/src/auto/cancellable.rs

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,9 @@
22
// from gir-files (https://github.com/gtk-rs/gir-files)
33
// DO NOT EDIT
44

5-
use glib::object::Cast;
65
use glib::object::IsA;
7-
use glib::signal::connect_raw;
8-
use glib::signal::SignalHandlerId;
96
use glib::translate::*;
10-
use std::boxed::Box as Box_;
117
use std::fmt;
12-
use std::mem::transmute;
138
use std::ptr;
149

1510
glib::wrapper! {
@@ -49,12 +44,6 @@ pub trait CancellableExt: 'static {
4944
#[doc(alias = "g_cancellable_cancel")]
5045
fn cancel(&self);
5146

52-
//#[doc(alias = "g_cancellable_connect")]
53-
//fn connect<P: Fn() + Send + Sync + 'static>(&self, callback: P, data: /*Unimplemented*/Option<Fundamental: Pointer>) -> libc::c_ulong;
54-
55-
#[doc(alias = "g_cancellable_disconnect")]
56-
fn disconnect(&self, handler_id: libc::c_ulong);
57-
5847
#[doc(alias = "g_cancellable_get_fd")]
5948
#[doc(alias = "get_fd")]
6049
fn fd(&self) -> i32;
@@ -76,9 +65,6 @@ pub trait CancellableExt: 'static {
7665

7766
#[doc(alias = "g_cancellable_set_error_if_cancelled")]
7867
fn set_error_if_cancelled(&self) -> Result<(), glib::Error>;
79-
80-
#[doc(alias = "cancelled")]
81-
fn connect_cancelled<F: Fn(&Self) + Send + Sync + 'static>(&self, f: F) -> SignalHandlerId;
8268
}
8369

8470
impl<O: IsA<Cancellable>> CancellableExt for O {
@@ -88,16 +74,6 @@ impl<O: IsA<Cancellable>> CancellableExt for O {
8874
}
8975
}
9076

91-
//fn connect<P: Fn() + Send + Sync + 'static>(&self, callback: P, data: /*Unimplemented*/Option<Fundamental: Pointer>) -> libc::c_ulong {
92-
// unsafe { TODO: call ffi:g_cancellable_connect() }
93-
//}
94-
95-
fn disconnect(&self, handler_id: libc::c_ulong) {
96-
unsafe {
97-
ffi::g_cancellable_disconnect(self.as_ref().to_glib_none().0, handler_id);
98-
}
99-
}
100-
10177
fn fd(&self) -> i32 {
10278
unsafe { ffi::g_cancellable_get_fd(self.as_ref().to_glib_none().0) }
10379
}
@@ -147,30 +123,6 @@ impl<O: IsA<Cancellable>> CancellableExt for O {
147123
}
148124
}
149125
}
150-
151-
fn connect_cancelled<F: Fn(&Self) + Send + Sync + 'static>(&self, f: F) -> SignalHandlerId {
152-
unsafe extern "C" fn cancelled_trampoline<
153-
P: IsA<Cancellable>,
154-
F: Fn(&P) + Send + Sync + 'static,
155-
>(
156-
this: *mut ffi::GCancellable,
157-
f: glib::ffi::gpointer,
158-
) {
159-
let f: &F = &*(f as *const F);
160-
f(Cancellable::from_glib_borrow(this).unsafe_cast_ref())
161-
}
162-
unsafe {
163-
let f: Box_<F> = Box_::new(f);
164-
connect_raw(
165-
self.as_ptr() as *mut _,
166-
b"cancelled\0".as_ptr() as *const _,
167-
Some(transmute::<_, unsafe extern "C" fn()>(
168-
cancelled_trampoline::<Self, F> as *const (),
169-
)),
170-
Box_::into_raw(f),
171-
)
172-
}
173-
}
174126
}
175127

176128
impl fmt::Display for Cancellable {

gio/src/cancellable.rs

Lines changed: 162 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,167 @@
11
// Take a look at the license at the top of the repository in the LICENSE file.
22

33
use crate::prelude::*;
4+
use crate::Cancellable;
5+
use futures_channel::oneshot;
6+
use futures_core::Future;
7+
use glib::{translate::*, IsA};
8+
use std::num::NonZeroU64;
49

5-
#[test]
6-
fn check_callback() {
7-
let c = crate::Cancellable::new();
8-
c.connect_cancelled(|_| {});
9-
c.cancel(); // if it doesn't crash at this point, then we're good to go!
10+
// rustdoc-stripper-ignore-next
11+
/// The id of a cancelled handler that is returned by `CancellableExtManual::connect`. This type is
12+
/// analogous to [`glib::SignalHandlerId`].
13+
#[derive(Debug, Eq, PartialEq)]
14+
#[repr(transparent)]
15+
pub struct CancelledHandlerId(NonZeroU64);
16+
17+
impl CancelledHandlerId {
18+
// rustdoc-stripper-ignore-next
19+
/// Returns the internal signal handler ID.
20+
pub unsafe fn as_raw(&self) -> libc::c_ulong {
21+
self.0.get() as libc::c_ulong
22+
}
23+
}
24+
25+
impl TryFromGlib<libc::c_ulong> for CancelledHandlerId {
26+
type Error = GlibNoneError;
27+
#[inline]
28+
unsafe fn try_from_glib(val: libc::c_ulong) -> Result<Self, GlibNoneError> {
29+
NonZeroU64::new(val as u64).map(Self).ok_or(GlibNoneError)
30+
}
31+
}
32+
33+
pub trait CancellableExtManual {
34+
// rustdoc-stripper-ignore-next
35+
/// Convenience function to connect to the `signal::Cancellable::cancelled` signal. Also
36+
/// handles the race condition that may happen if the cancellable is cancelled right before
37+
/// connecting. If the operation is cancelled from another thread, `callback` will be called
38+
/// in the thread that cancelled the operation, not the thread that is running the operation.
39+
/// This may be the main thread, so the callback should not do something that can block.
40+
///
41+
/// `callback` is called at most once, either directly at the time of the connect if `self` is
42+
/// already cancelled, or when `self` is cancelled in some thread.
43+
///
44+
/// Since GLib 2.40, the lock protecting `self` is not held when `callback` is invoked. This
45+
/// lifts a restriction in place for earlier GLib versions which now makes it easier to write
46+
/// cleanup code that unconditionally invokes e.g.
47+
/// [`CancellableExt::cancel()`][crate::prelude::CancellableExt::cancel()].
48+
///
49+
/// # Returns
50+
///
51+
/// The id of the signal handler or `None` if `self` has already been cancelled.
52+
#[doc(alias = "g_cancellable_connect")]
53+
fn connect_cancelled<F: FnOnce(&Self) + Send + 'static>(
54+
&self,
55+
callback: F,
56+
) -> Option<CancelledHandlerId>;
57+
// rustdoc-stripper-ignore-next
58+
/// Disconnects a handler from a cancellable instance. Additionally, in the event that a signal
59+
/// handler is currently running, this call will block until the handler has finished. Calling
60+
/// this function from a callback registered with [`Self::connect_cancelled`] will therefore
61+
/// result in a deadlock.
62+
///
63+
/// This avoids a race condition where a thread cancels at the same time as the cancellable
64+
/// operation is finished and the signal handler is removed.
65+
#[doc(alias = "g_cancellable_disconnect")]
66+
fn disconnect_cancelled(&self, id: CancelledHandlerId);
67+
// rustdoc-stripper-ignore-next
68+
/// Returns a `Future` that completes when the cancellable becomes cancelled. Completes
69+
/// immediately if the cancellable is already cancelled.
70+
fn future(&self) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>>;
71+
}
72+
73+
impl<O: IsA<Cancellable>> CancellableExtManual for O {
74+
#[doc(alias = "g_cancellable_connect")]
75+
fn connect_cancelled<F: FnOnce(&Self) + Send + 'static>(
76+
&self,
77+
callback: F,
78+
) -> Option<CancelledHandlerId> {
79+
unsafe extern "C" fn connect_trampoline<P: IsA<Cancellable>, F: FnOnce(&P)>(
80+
this: *mut ffi::GCancellable,
81+
callback: glib::ffi::gpointer,
82+
) {
83+
let callback: &mut Option<F> = &mut *(callback as *mut Option<F>);
84+
let callback = callback
85+
.take()
86+
.expect("Cancellable::cancel() closure called multiple times");
87+
callback(Cancellable::from_glib_borrow(this).unsafe_cast_ref())
88+
}
89+
90+
unsafe extern "C" fn destroy_closure<F>(ptr: glib::ffi::gpointer) {
91+
Box::<Option<F>>::from_raw(ptr as *mut _);
92+
}
93+
94+
let callback: Box<Option<F>> = Box::new(Some(callback));
95+
unsafe {
96+
from_glib(ffi::g_cancellable_connect(
97+
self.as_ptr() as *mut _,
98+
Some(std::mem::transmute::<_, unsafe extern "C" fn()>(
99+
connect_trampoline::<Self, F> as *const (),
100+
)),
101+
Box::into_raw(callback) as *mut _,
102+
Some(destroy_closure::<F>),
103+
))
104+
}
105+
}
106+
#[doc(alias = "g_cancellable_disconnect")]
107+
fn disconnect_cancelled(&self, id: CancelledHandlerId) {
108+
unsafe { ffi::g_cancellable_disconnect(self.as_ptr() as *mut _, id.as_raw()) };
109+
}
110+
fn future(&self) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>> {
111+
let cancellable = self.as_ref().clone();
112+
let (tx, rx) = oneshot::channel();
113+
let id = cancellable.connect_cancelled(move |_| {
114+
let _ = tx.send(());
115+
});
116+
Box::pin(async move {
117+
rx.await.unwrap();
118+
if let Some(id) = id {
119+
cancellable.disconnect_cancelled(id);
120+
}
121+
})
122+
}
123+
}
124+
125+
#[cfg(test)]
126+
mod tests {
127+
use super::*;
128+
129+
#[test]
130+
fn cancellable_callback() {
131+
let c = Cancellable::new();
132+
let id = c.connect_cancelled(|_| {});
133+
c.cancel(); // if it doesn't crash at this point, then we're good to go!
134+
c.disconnect_cancelled(id.unwrap());
135+
}
136+
137+
#[test]
138+
fn cancellable_future() {
139+
let c = Cancellable::new();
140+
c.cancel();
141+
glib::MainContext::new().block_on(c.future());
142+
}
143+
144+
#[test]
145+
fn cancellable_future_thread() {
146+
let cancellable = Cancellable::new();
147+
let c = cancellable.clone();
148+
std::thread::spawn(move || c.cancel()).join().unwrap();
149+
glib::MainContext::new().block_on(cancellable.future());
150+
}
151+
152+
#[test]
153+
fn cancellable_future_delayed() {
154+
let ctx = glib::MainContext::new();
155+
let c = Cancellable::new();
156+
let (tx, rx) = oneshot::channel();
157+
{
158+
let c = c.clone();
159+
ctx.spawn_local(async move {
160+
c.future().await;
161+
tx.send(()).unwrap();
162+
});
163+
}
164+
std::thread::spawn(move || c.cancel()).join().unwrap();
165+
ctx.block_on(rx).unwrap();
166+
}
10167
}

gio/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ pub use glib;
1414

1515
mod app_info;
1616
mod application;
17-
#[cfg(test)]
1817
mod cancellable;
1918
mod converter;
2019
mod data_input_stream;

gio/src/prelude.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub use crate::auto::traits::*;
1111
#[cfg(any(feature = "v2_60", feature = "dox"))]
1212
pub use crate::app_info::AppInfoExtManual;
1313
pub use crate::application::*;
14+
pub use crate::cancellable::*;
1415
pub use crate::converter::*;
1516
pub use crate::data_input_stream::DataInputStreamExtManual;
1617
pub use crate::dbus_proxy::DBusProxyExtManual;

0 commit comments

Comments
 (0)