Skip to main content

gio/
task.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{boxed::Box as Box_, future::Future, mem::transmute, panic, ptr};
4
5use glib::{
6    prelude::*,
7    signal::{SignalHandlerId, connect_raw},
8    translate::*,
9};
10
11use futures_channel::oneshot;
12
13use crate::{AsyncResult, Cancellable, ffi};
14
15glib::wrapper! {
16    // rustdoc-stripper-ignore-next
17    /// `LocalTask` provides idiomatic access to gio's `GTask` API, for
18    /// instance by being generic over their value type, while not completely departing
19    /// from the underlying C API. `LocalTask` does not require its value to be `Send`
20    /// and `Sync` and thus is useful to to implement gio style asynchronous
21    /// tasks that run in the glib main loop. If you need to run tasks in threads
22    /// see the `Task` type.
23    ///
24    /// The constructors of `LocalTask` and `Task` is marked as unsafe because this API does
25    /// not allow to automatically enforce all the invariants required to be a completely
26    /// safe abstraction. See the `Task` type for more details.
27    #[doc(alias = "GTask")]
28    pub struct LocalTask<V: ValueType>(Object<ffi::GTask, ffi::GTaskClass>) @implements AsyncResult;
29
30    match fn {
31        type_ => || ffi::g_task_get_type(),
32    }
33}
34
35glib::wrapper! {
36    // rustdoc-stripper-ignore-next
37    /// `Task` provides idiomatic access to gio's `GTask` API, for
38    /// instance by being generic over their value type, while not completely departing
39    /// from the underlying C API. `Task` is `Send` and `Sync` and requires its value to
40    /// also be `Send` and `Sync`, thus is useful to to implement gio style asynchronous
41    /// tasks that run in threads. If you need to only run tasks in glib main loop
42    /// see the `LocalTask` type.
43    ///
44    /// The constructors of `LocalTask` and `Task` is marked as unsafe because this API does
45    /// not allow to automatically enforce all the invariants required to be a completely
46    /// safe abstraction. The caller is responsible to ensure the following requirements
47    /// are satisfied
48    ///
49    /// * You should not create a `LocalTask`, upcast it to a `glib::Object` and then
50    ///   downcast it to a `Task`, as this will bypass the thread safety requirements
51    /// * You should ensure that the `return_result`, `return_error_if_cancelled` and
52    ///   `propagate()` methods are only called once.
53    // rustdoc-stripper-ignore-next-stop
54    ///  results, such as when passing
55    ///   `0` to [`InputStreamExtManual::read_async()`][crate::prelude::InputStreamExtManual::read_async()]).
56    ///
57    /// ## Thread-safety considerations
58    ///
59    /// Due to some infelicities in the API design, there is a
60    /// thread-safety concern that users of `GTask` have to be aware of:
61    ///
62    /// If the `main` thread drops its last reference to the source object
63    /// or the task data before the task is finalized, then the finalizers
64    /// of these objects may be called on the worker thread.
65    ///
66    /// This is a problem if the finalizers use non-threadsafe API, and
67    /// can lead to hard-to-debug crashes. Possible workarounds include:
68    ///
69    /// - Clear task data in a signal handler for `notify::completed`
70    /// - Keep iterating a main context in the main thread and defer
71    ///   dropping the reference to the source object to that main
72    ///   context when the task is finalized
73    ///
74    /// ## Properties
75    ///
76    ///
77    /// #### `completed`
78    ///  s callback, immediately after that callback is invoked.
79    ///
80    /// Readable
81    ///
82    /// # Implements
83    ///
84    /// [`trait@glib::ObjectExt`], [`AsyncResultExt`][trait@crate::prelude::AsyncResultExt]
85    #[doc(alias = "GTask")]
86    pub struct Task<V: ValueType + Send>(Object<ffi::GTask, ffi::GTaskClass>) @implements AsyncResult;
87
88    match fn {
89        type_ => || ffi::g_task_get_type(),
90    }
91}
92
93macro_rules! task_impl {
94    ($name:ident $(, @bound: $bound:tt)? $(, @safety: $safety:tt)?) => {
95        impl <V: Into<glib::Value> + ValueType $(+ $bound)?> $name<V> {
96            #[doc(alias = "g_task_new")]
97            #[allow(unused_unsafe)]
98            pub unsafe fn new<S, P, Q>(
99                source_object: Option<&S>,
100                cancellable: Option<&P>,
101                callback: Q,
102            ) -> Self
103            where
104                S: IsA<glib::Object> $(+ $bound)?,
105                P: IsA<Cancellable>,
106                Q: FnOnce($name<V>, Option<&S>) $(+ $bound)? + 'static,
107            {
108                let callback_data = Box_::new(callback);
109                unsafe extern "C" fn trampoline<
110                    S: IsA<glib::Object> $(+ $bound)?,
111                    V: ValueType $(+ $bound)?,
112                    Q: FnOnce($name<V>, Option<&S>) $(+ $bound)? + 'static,
113                >(
114                    source_object: *mut glib::gobject_ffi::GObject,
115                    res: *mut ffi::GAsyncResult,
116                    user_data: glib::ffi::gpointer,
117                ) { unsafe {
118                    let callback: Box_<Q> = Box::from_raw(user_data as *mut _);
119                    let task = AsyncResult::from_glib_none(res)
120                        .downcast::<$name<V>>()
121                        .unwrap();
122                    let source_object = Option::<glib::Object>::from_glib_borrow(source_object);
123                    callback(
124                        task,
125                        source_object.as_ref().as_ref().map(|s| s.unsafe_cast_ref()),
126                    );
127                }}
128                let callback = trampoline::<S, V, Q>;
129                unsafe {
130                    from_glib_full(ffi::g_task_new(
131                        source_object.map(|p| p.as_ref()).to_glib_none().0,
132                        cancellable.map(|p| p.as_ref()).to_glib_none().0,
133                        Some(callback),
134                        Box_::into_raw(callback_data) as *mut _,
135                    ))
136                }
137            }
138
139            #[doc(alias = "g_task_get_cancellable")]
140            #[doc(alias = "get_cancellable")]
141            pub fn cancellable(&self) -> Option<Cancellable> {
142                unsafe { from_glib_none(ffi::g_task_get_cancellable(self.to_glib_none().0)) }
143            }
144
145            #[doc(alias = "g_task_get_check_cancellable")]
146            #[doc(alias = "get_check_cancellable")]
147            pub fn is_check_cancellable(&self) -> bool {
148                unsafe { from_glib(ffi::g_task_get_check_cancellable(self.to_glib_none().0)) }
149            }
150
151            #[doc(alias = "g_task_set_check_cancellable")]
152            pub fn set_check_cancellable(&self, check_cancellable: bool) {
153                unsafe {
154                    ffi::g_task_set_check_cancellable(self.to_glib_none().0, check_cancellable.into_glib());
155                }
156            }
157
158            #[cfg(feature = "v2_60")]
159            #[cfg_attr(docsrs, doc(cfg(feature = "v2_60")))]
160            #[doc(alias = "g_task_set_name")]
161            pub fn set_name(&self, name: Option<&str>) {
162                unsafe {
163                    ffi::g_task_set_name(self.to_glib_none().0, name.to_glib_none().0);
164                }
165            }
166
167            #[doc(alias = "g_task_set_return_on_cancel")]
168            pub fn set_return_on_cancel(&self, return_on_cancel: bool) -> bool {
169                unsafe {
170                    from_glib(ffi::g_task_set_return_on_cancel(
171                        self.to_glib_none().0,
172                        return_on_cancel.into_glib(),
173                    ))
174                }
175            }
176
177            #[doc(alias = "g_task_is_valid")]
178            pub fn is_valid(
179                result: &impl IsA<AsyncResult>,
180                source_object: Option<&impl IsA<glib::Object>>,
181            ) -> bool {
182                unsafe {
183                    from_glib(ffi::g_task_is_valid(
184                        result.as_ref().to_glib_none().0,
185                        source_object.map(|p| p.as_ref()).to_glib_none().0,
186                    ))
187                }
188            }
189
190            #[doc(alias = "get_priority")]
191            #[doc(alias = "g_task_get_priority")]
192            pub fn priority(&self) -> glib::source::Priority {
193                unsafe { FromGlib::from_glib(ffi::g_task_get_priority(self.to_glib_none().0)) }
194            }
195
196            #[doc(alias = "g_task_set_priority")]
197            pub fn set_priority(&self, priority: glib::source::Priority) {
198                unsafe {
199                    ffi::g_task_set_priority(self.to_glib_none().0, priority.into_glib());
200                }
201            }
202
203            #[doc(alias = "g_task_get_completed")]
204            #[doc(alias = "get_completed")]
205            pub fn is_completed(&self) -> bool {
206                unsafe { from_glib(ffi::g_task_get_completed(self.to_glib_none().0)) }
207            }
208
209            #[doc(alias = "g_task_get_context")]
210            #[doc(alias = "get_context")]
211            pub fn context(&self) -> glib::MainContext {
212                unsafe { from_glib_none(ffi::g_task_get_context(self.to_glib_none().0)) }
213            }
214
215            #[cfg(feature = "v2_60")]
216            #[cfg_attr(docsrs, doc(cfg(feature = "v2_60")))]
217            #[doc(alias = "g_task_get_name")]
218            #[doc(alias = "get_name")]
219            pub fn name(&self) -> Option<glib::GString> {
220                unsafe { from_glib_none(ffi::g_task_get_name(self.to_glib_none().0)) }
221            }
222
223            #[doc(alias = "g_task_get_return_on_cancel")]
224            #[doc(alias = "get_return_on_cancel")]
225            pub fn is_return_on_cancel(&self) -> bool {
226                unsafe { from_glib(ffi::g_task_get_return_on_cancel(self.to_glib_none().0)) }
227            }
228
229            #[doc(alias = "g_task_had_error")]
230            pub fn had_error(&self) -> bool {
231                unsafe { from_glib(ffi::g_task_had_error(self.to_glib_none().0)) }
232            }
233
234            #[doc(alias = "completed")]
235            pub fn connect_completed_notify<F>(&self, f: F) -> SignalHandlerId
236            where
237                F: Fn(&$name<V>) $(+ $bound)? + 'static,
238            {
239                unsafe extern "C" fn notify_completed_trampoline<V, F>(
240                    this: *mut ffi::GTask,
241                    _param_spec: glib::ffi::gpointer,
242                    f: glib::ffi::gpointer,
243                ) where
244                    V: ValueType $(+ $bound)?,
245                    F: Fn(&$name<V>) + 'static,
246                { unsafe {
247                    let f: &F = &*(f as *const F);
248                    f(&from_glib_borrow(this))
249                }}
250                unsafe {
251                    let f: Box_<F> = Box_::new(f);
252                    connect_raw(
253                        self.as_ptr() as *mut _,
254                        b"notify::completed\0".as_ptr() as *const _,
255                        Some(transmute::<*const (), unsafe extern "C" fn()>(
256                            notify_completed_trampoline::<V, F> as *const (),
257                        )),
258                        Box_::into_raw(f),
259                    )
260                }
261            }
262
263            // the following functions are marked unsafe since they cannot be called
264            // more than once, but we have no way to enforce that since the task can be cloned
265
266            #[doc(alias = "g_task_return_error_if_cancelled")]
267            #[allow(unused_unsafe)]
268            pub $($safety)? fn return_error_if_cancelled(&self) -> bool {
269                unsafe { from_glib(ffi::g_task_return_error_if_cancelled(self.to_glib_none().0)) }
270            }
271
272            // rustdoc-stripper-ignore-next
273            /// Set the result of the task
274            ///
275            /// # Safety
276            ///
277            /// The value must be read with [`Task::propagate`],
278            /// `g_task_propagate_value` or `g_task_propagate_pointer`.
279            #[doc(alias = "g_task_return_value")]
280            #[doc(alias = "g_task_return_pointer")]
281            #[doc(alias = "g_task_return_error")]
282            #[allow(unused_unsafe)]
283            pub $($safety)? fn return_result(self, result: Result<V, glib::Error>) {
284                #[cfg(not(feature = "v2_64"))]
285                unsafe extern "C" fn value_free(value: *mut libc::c_void) { unsafe {
286                    let _: glib::Value = from_glib_full(value as *mut glib::gobject_ffi::GValue);
287                }}
288
289                match result {
290                    #[cfg(feature = "v2_64")]
291                    Ok(v) => unsafe {
292                        ffi::g_task_return_value(
293                            self.to_glib_none().0,
294                            v.to_value().to_glib_none().0 as *mut _,
295                        )
296                    },
297                    #[cfg(not(feature = "v2_64"))]
298                    Ok(v) => unsafe {
299                        let v: glib::Value = v.into();
300                        ffi::g_task_return_pointer(
301                            self.to_glib_none().0,
302                            <glib::Value as glib::translate::IntoGlibPtr::<*mut glib::gobject_ffi::GValue>>::into_glib_ptr(v) as glib::ffi::gpointer,
303                            Some(value_free),
304                        )
305                    },
306                    Err(e) => unsafe {
307                        ffi::g_task_return_error(self.to_glib_none().0, e.into_glib_ptr());
308                    },
309                }
310            }
311
312            // rustdoc-stripper-ignore-next
313            /// Set the result of the task as a boolean
314            ///
315            /// # Safety
316            ///
317            /// The value must be read with [`Task::propagate_boolean`],
318            /// or `g_task_propagate_boolean`.
319            #[doc(alias = "g_task_return_boolean")]
320            #[allow(unused_unsafe)]
321            pub $($safety)? fn return_boolean_result(self, result: Result<bool, glib::Error>) {
322                match result {
323                    Ok(v) =>  unsafe { ffi::g_task_return_boolean(self.to_glib_none().0, v as i32) },
324                    Err(e) => unsafe { ffi::g_task_return_error(self.to_glib_none().0, e.into_glib_ptr()) },
325                }
326            }
327
328            // rustdoc-stripper-ignore-next
329            /// Set the result of the task as an int
330            ///
331            /// # Safety
332            ///
333            /// The value must be read with [`Task::propagate_int`],
334            /// or `g_task_propagate_int`.
335            #[doc(alias = "g_task_return_int")]
336            #[allow(unused_unsafe)]
337            pub $($safety)? fn return_int_result(self, result: Result<isize, glib::Error>) {
338                match result {
339                    Ok(v) =>  unsafe { ffi::g_task_return_int(self.to_glib_none().0, v) },
340                    Err(e) => unsafe { ffi::g_task_return_error(self.to_glib_none().0, e.into_glib_ptr()) },
341                }
342            }
343
344
345            // rustdoc-stripper-ignore-next
346            /// Gets the result of the task and transfers ownership of it
347            ///
348            /// # Safety
349            ///
350            /// This must only be called once, and only if the result was set
351            /// via [`Task::return_result`], `g_task_return_value` or
352            /// `g_task_return_pointer`.
353            #[doc(alias = "g_task_propagate_value")]
354            #[doc(alias = "g_task_propagate_pointer")]
355            #[allow(unused_unsafe)]
356            pub unsafe fn propagate(self) -> Result<V, glib::Error> {
357                let mut error = ptr::null_mut();
358
359                unsafe {
360                    #[cfg(feature = "v2_64")]
361                    {
362                        let mut value = glib::Value::uninitialized();
363                        ffi::g_task_propagate_value(
364                            self.to_glib_none().0,
365                            value.to_glib_none_mut().0,
366                            &mut error,
367                        );
368
369                        if error.is_null() {
370                            Ok(V::from_value(&value))
371                        } else {
372                            Err(from_glib_full(error))
373                        }
374                    }
375
376                    #[cfg(not(feature = "v2_64"))]
377                    {
378                        let value = ffi::g_task_propagate_pointer(self.to_glib_none().0, &mut error);
379
380                        if error.is_null() {
381                            let value = Option::<glib::Value>::from_glib_full(
382                                value as *mut glib::gobject_ffi::GValue,
383                            )
384                            .expect("Task::propagate() called before Task::return_result()");
385                            Ok(V::from_value(&value))
386                        } else {
387                            Err(from_glib_full(error))
388                        }
389                    }
390                }
391            }
392
393            // rustdoc-stripper-ignore-next
394            /// Gets the result of the task as a boolean, or the error
395            ///
396            /// # Safety
397            ///
398            /// This must only be called once, and only if the result was set
399            /// via [`Task::return_boolean_result`], or `g_task_return_boolean`.
400            #[doc(alias = "g_task_propagate_boolean")]
401            #[allow(unused_unsafe)]
402            pub unsafe fn propagate_boolean(self) -> Result<bool, glib::Error> {
403                let mut error = ptr::null_mut();
404
405                unsafe {
406                    let res = ffi::g_task_propagate_boolean(self.to_glib_none().0, &mut error);
407
408                    if error.is_null() {
409                        Ok(res != 0)
410                    } else {
411                        Err(from_glib_full(error))
412                    }
413                }
414            }
415
416            // rustdoc-stripper-ignore-next
417            /// Gets the result of the task as an int, or the error
418            ///
419            /// # Safety
420            ///
421            /// This must only be called once, and only if the result was set
422            /// via [`Task::return_int_result`], or `g_task_return_int`.
423            #[doc(alias = "g_task_propagate_int")]
424            #[allow(unused_unsafe)]
425            pub unsafe fn propagate_int(self) -> Result<isize, glib::Error> {
426                let mut error = ptr::null_mut();
427
428                unsafe {
429                    let res = ffi::g_task_propagate_int(self.to_glib_none().0, &mut error);
430
431                    if error.is_null() {
432                        Ok(res)
433                    } else {
434                        Err(from_glib_full(error))
435                    }
436                }
437            }
438        }
439    }
440}
441
442task_impl!(LocalTask);
443task_impl!(Task, @bound: Send, @safety: unsafe);
444
445impl<V: ValueType + Send> Task<V> {
446    #[doc(alias = "g_task_run_in_thread")]
447    pub fn run_in_thread<S, Q>(&self, task_func: Q)
448    where
449        S: IsA<glib::Object> + Send,
450        Q: FnOnce(Self, Option<&S>, Option<&Cancellable>) + Send + 'static,
451    {
452        let task_func_data = Box_::new(task_func);
453
454        // We store the func pointer into the task data.
455        // We intentionally do not expose a way to set the task data in the bindings.
456        // If we detect that the task data is set, there is not much we can do, so we panic.
457        unsafe {
458            assert!(
459                ffi::g_task_get_task_data(self.to_glib_none().0).is_null(),
460                "Task data was manually set or the task was run thread multiple times"
461            );
462
463            ffi::g_task_set_task_data(
464                self.to_glib_none().0,
465                Box_::into_raw(task_func_data) as *mut _,
466                None,
467            );
468        }
469
470        unsafe extern "C" fn trampoline<V, S, Q>(
471            task: *mut ffi::GTask,
472            source_object: *mut glib::gobject_ffi::GObject,
473            user_data: glib::ffi::gpointer,
474            cancellable: *mut ffi::GCancellable,
475        ) where
476            V: ValueType + Send,
477            S: IsA<glib::Object> + Send,
478            Q: FnOnce(Task<V>, Option<&S>, Option<&Cancellable>) + Send + 'static,
479        {
480            unsafe {
481                let task = Task::from_glib_none(task);
482                let source_object = Option::<glib::Object>::from_glib_borrow(source_object);
483                let cancellable = Option::<Cancellable>::from_glib_borrow(cancellable);
484                let task_func: Box_<Q> = Box::from_raw(user_data as *mut _);
485                task_func(
486                    task,
487                    source_object.as_ref().as_ref().map(|s| s.unsafe_cast_ref()),
488                    cancellable.as_ref().as_ref(),
489                );
490            }
491        }
492
493        let task_func = trampoline::<V, S, Q>;
494        unsafe {
495            ffi::g_task_run_in_thread(self.to_glib_none().0, Some(task_func));
496        }
497    }
498}
499
500unsafe impl<V: ValueType + Send> Send for Task<V> {}
501unsafe impl<V: ValueType + Send> Sync for Task<V> {}
502
503// rustdoc-stripper-ignore-next
504/// A handle to a task running on the I/O thread pool.
505///
506/// Like [`std::thread::JoinHandle`] for a blocking I/O task rather than a thread. The return value
507/// from the task can be retrieved by awaiting on this handle. Dropping the handle "detaches" the
508/// task, allowing it to complete but discarding the return value.
509#[derive(Debug)]
510pub struct JoinHandle<T> {
511    rx: oneshot::Receiver<std::thread::Result<T>>,
512}
513
514impl<T> JoinHandle<T> {
515    #[inline]
516    fn new() -> (Self, oneshot::Sender<std::thread::Result<T>>) {
517        let (tx, rx) = oneshot::channel();
518        (Self { rx }, tx)
519    }
520}
521
522impl<T> Future for JoinHandle<T> {
523    type Output = std::thread::Result<T>;
524    #[inline]
525    fn poll(
526        mut self: std::pin::Pin<&mut Self>,
527        cx: &mut std::task::Context<'_>,
528    ) -> std::task::Poll<Self::Output> {
529        std::pin::Pin::new(&mut self.rx)
530            .poll(cx)
531            .map(|r| r.unwrap())
532    }
533}
534
535impl<T> futures_core::FusedFuture for JoinHandle<T> {
536    #[inline]
537    fn is_terminated(&self) -> bool {
538        self.rx.is_terminated()
539    }
540}
541
542// rustdoc-stripper-ignore-next
543/// Runs a blocking I/O task on the I/O thread pool.
544///
545/// Calls `func` on the internal Gio thread pool for blocking I/O operations. The thread pool is
546/// shared with other Gio async I/O operations, and may rate-limit the tasks it receives. Callers
547/// may want to avoid blocking indefinitely by making sure blocking calls eventually time out.
548///
549/// This function should not be used to spawn async tasks. Instead, use
550/// [`glib::MainContext::spawn`] or [`glib::MainContext::spawn_local`] to run a future.
551pub fn spawn_blocking<T, F>(func: F) -> JoinHandle<T>
552where
553    T: Send + 'static,
554    F: FnOnce() -> T + Send + 'static,
555{
556    // use Cancellable::NONE as source obj to fulfill `Send` requirement
557    let task = unsafe { Task::<bool>::new(Cancellable::NONE, Cancellable::NONE, |_, _| {}) };
558    let (join, tx) = JoinHandle::new();
559    task.run_in_thread(move |task, _: Option<&Cancellable>, _| {
560        let res = panic::catch_unwind(panic::AssertUnwindSafe(func));
561        let _ = tx.send(res);
562        unsafe { ffi::g_task_return_pointer(task.to_glib_none().0, ptr::null_mut(), None) }
563    });
564
565    join
566}
567
568#[cfg(test)]
569mod test {
570    use super::*;
571    use crate::{prelude::*, test_util::run_async_local};
572
573    #[test]
574    fn test_int_value_async_result() {
575        let fut = run_async_local(|tx, l| {
576            let cancellable = crate::Cancellable::new();
577            let task = unsafe {
578                crate::LocalTask::new(
579                    None,
580                    Some(&cancellable),
581                    move |t: LocalTask<i32>, _b: Option<&glib::Object>| {
582                        tx.send(t.propagate()).unwrap();
583                        l.quit();
584                    },
585                )
586            };
587            task.return_result(Ok(100_i32));
588        });
589
590        match fut {
591            Err(_) => panic!(),
592            Ok(i) => assert_eq!(i, 100),
593        }
594    }
595
596    #[test]
597    fn test_boolean_async_result() {
598        let fut = run_async_local(|tx, l| {
599            let cancellable = crate::Cancellable::new();
600            let task = unsafe {
601                crate::LocalTask::new(
602                    None,
603                    Some(&cancellable),
604                    move |t: LocalTask<bool>, _b: Option<&glib::Object>| {
605                        tx.send(t.propagate_boolean()).unwrap();
606                        l.quit();
607                    },
608                )
609            };
610            task.return_boolean_result(Ok(true));
611        });
612
613        match fut {
614            Err(_) => panic!(),
615            Ok(i) => assert!(i),
616        }
617    }
618
619    #[test]
620    fn test_int_async_result() {
621        let fut = run_async_local(|tx, l| {
622            let cancellable = crate::Cancellable::new();
623            let task = unsafe {
624                crate::LocalTask::new(
625                    None,
626                    Some(&cancellable),
627                    move |t: LocalTask<i32>, _b: Option<&glib::Object>| {
628                        tx.send(t.propagate_int()).unwrap();
629                        l.quit();
630                    },
631                )
632            };
633            task.return_int_result(Ok(100_isize));
634        });
635
636        match fut {
637            Err(_) => panic!(),
638            Ok(i) => assert_eq!(i, 100),
639        }
640    }
641
642    #[test]
643    fn test_object_async_result() {
644        use glib::subclass::prelude::*;
645        pub struct MySimpleObjectPrivate {
646            pub size: std::cell::RefCell<Option<i64>>,
647        }
648
649        #[glib::object_subclass]
650        impl ObjectSubclass for MySimpleObjectPrivate {
651            const NAME: &'static str = "MySimpleObjectPrivate";
652            type Type = MySimpleObject;
653
654            fn new() -> Self {
655                Self {
656                    size: std::cell::RefCell::new(Some(100)),
657                }
658            }
659        }
660
661        impl ObjectImpl for MySimpleObjectPrivate {}
662
663        glib::wrapper! {
664            pub struct MySimpleObject(ObjectSubclass<MySimpleObjectPrivate>);
665        }
666
667        impl MySimpleObject {
668            pub fn new() -> Self {
669                glib::Object::new()
670            }
671
672            #[doc(alias = "get_size")]
673            pub fn size(&self) -> Option<i64> {
674                *self.imp().size.borrow()
675            }
676
677            pub fn set_size(&self, size: i64) {
678                self.imp().size.borrow_mut().replace(size);
679            }
680        }
681
682        impl Default for MySimpleObject {
683            fn default() -> Self {
684                Self::new()
685            }
686        }
687
688        let fut = run_async_local(|tx, l| {
689            let cancellable = crate::Cancellable::new();
690            let task = unsafe {
691                crate::LocalTask::new(
692                    None,
693                    Some(&cancellable),
694                    move |t: LocalTask<glib::Object>, _b: Option<&glib::Object>| {
695                        tx.send(t.propagate()).unwrap();
696                        l.quit();
697                    },
698                )
699            };
700            let my_object = MySimpleObject::new();
701            my_object.set_size(100);
702            task.return_result(Ok(my_object.upcast::<glib::Object>()));
703        });
704
705        match fut {
706            Err(_) => panic!(),
707            Ok(o) => {
708                let o = o.downcast::<MySimpleObject>().unwrap();
709                assert_eq!(o.size(), Some(100));
710            }
711        }
712    }
713
714    #[test]
715    fn test_error() {
716        let fut = run_async_local(|tx, l| {
717            let cancellable = crate::Cancellable::new();
718            let task = unsafe {
719                crate::LocalTask::new(
720                    None,
721                    Some(&cancellable),
722                    move |t: LocalTask<i32>, _b: Option<&glib::Object>| {
723                        tx.send(t.propagate()).unwrap();
724                        l.quit();
725                    },
726                )
727            };
728            task.return_result(Err(glib::Error::new(
729                crate::IOErrorEnum::WouldBlock,
730                "WouldBlock",
731            )));
732        });
733
734        match fut {
735            Err(e) => match e.kind().unwrap() {
736                crate::IOErrorEnum::WouldBlock => {}
737                _ => panic!(),
738            },
739            Ok(_) => panic!(),
740        }
741    }
742
743    #[test]
744    fn test_cancelled() {
745        let fut = run_async_local(|tx, l| {
746            let cancellable = crate::Cancellable::new();
747            let task = unsafe {
748                crate::LocalTask::new(
749                    None,
750                    Some(&cancellable),
751                    move |t: LocalTask<i32>, _b: Option<&glib::Object>| {
752                        tx.send(t.propagate()).unwrap();
753                        l.quit();
754                    },
755                )
756            };
757            cancellable.cancel();
758            task.return_error_if_cancelled();
759        });
760
761        match fut {
762            Err(e) => match e.kind().unwrap() {
763                crate::IOErrorEnum::Cancelled => {}
764                _ => panic!(),
765            },
766            Ok(_) => panic!(),
767        }
768    }
769}