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 /// error);
55 /// if (error)
56 /// {
57 /// g_task_return_error (task, error);
58 /// return;
59 /// }
60 ///
61 /// // If the task has already been cancelled, then we don’t want to add
62 /// // the cake to the cake cache. Likewise, we don’t want to have the
63 /// // task get cancelled in the middle of updating the cache.
64 /// // g_task_set_return_on_cancel() will return [`true`] here if it managed
65 /// // to disable return-on-cancel, or [`false`] if the task was cancelled
66 /// // before it could.
67 /// if (g_task_set_return_on_cancel (task, FALSE))
68 /// {
69 /// // If the caller cancels at this point, their
70 /// // GAsyncReadyCallback won’t be invoked until we return,
71 /// // so we don’t have to worry that this code will run at
72 /// // the same time as that code does. But if there were
73 /// // other functions that might look at the cake cache,
74 /// // then we’d probably need a GMutex here as well.
75 /// baker_add_cake_to_cache (baker, cake);
76 /// g_task_return_pointer (task, cake, g_object_unref);
77 /// }
78 /// }
79 ///
80 /// void
81 /// baker_bake_cake_async (Baker *self,
82 /// guint radius,
83 /// CakeFlavor flavor,
84 /// CakeFrostingType frosting,
85 /// const char *message,
86 /// GCancellable *cancellable,
87 /// GAsyncReadyCallback callback,
88 /// gpointer user_data)
89 /// {
90 /// CakeData *cake_data;
91 /// GTask *task;
92 ///
93 /// cake_data = g_slice_new (CakeData);
94 ///
95 /// ...
96 ///
97 /// task = g_task_new (self, cancellable, callback, user_data);
98 /// g_task_set_task_data (task, cake_data, (GDestroyNotify) cake_data_free);
99 /// g_task_set_return_on_cancel (task, TRUE);
100 /// g_task_run_in_thread (task, bake_cake_thread);
101 /// }
102 ///
103 /// Cake *
104 /// baker_bake_cake_sync (Baker *self,
105 /// guint radius,
106 /// CakeFlavor flavor,
107 /// CakeFrostingType frosting,
108 /// const char *message,
109 /// GCancellable *cancellable,
110 /// GError **error)
111 /// {
112 /// CakeData *cake_data;
113 /// GTask *task;
114 /// Cake *cake;
115 ///
116 /// cake_data = g_slice_new (CakeData);
117 ///
118 /// ...
119 ///
120 /// task = g_task_new (self, cancellable, NULL, NULL);
121 /// g_task_set_task_data (task, cake_data, (GDestroyNotify) cake_data_free);
122 /// g_task_set_return_on_cancel (task, TRUE);
123 /// g_task_run_in_thread_sync (task, bake_cake_thread);
124 ///
125 /// cake = g_task_propagate_pointer (task, error);
126 /// g_object_unref (task);
127 /// return cake;
128 /// }
129 /// ```text
130 ///
131 /// ## Porting from `Gio::SimpleAsyncResult`
132 ///
133 /// `GTask`’s API attempts to be simpler than `Gio::SimpleAsyncResult`’s
134 /// in several ways:
135 ///
136 /// - You can save task-specific data with [`set_task_data()`][Self::set_task_data()], and
137 /// retrieve it later with [`task_data()`][Self::task_data()]. This replaces the
138 /// abuse of `Gio::SimpleAsyncResult::set_op_res_gpointer()` for the same
139 /// purpose with `Gio::SimpleAsyncResult`.
140 /// - In addition to the task data, `GTask` also keeps track of the
141 /// [priority](iface.AsyncResult.html#io-priority), [`Cancellable`][crate::Cancellable],
142 /// and [`glib::MainContext`][crate::glib::MainContext] associated with the task, so tasks that
143 /// consist of a chain of simpler asynchronous operations will have easy access
144 /// to those values when starting each sub-task.
145 /// - [`return_error_if_cancelled()`][Self::return_error_if_cancelled()] provides simplified
146 /// handling for cancellation. In addition, cancellation
147 /// overrides any other `GTask` return value by default, like
148 /// `Gio::SimpleAsyncResult` does when
149 /// `Gio::SimpleAsyncResult::set_check_cancellable()` is called.
150 /// (You can use [`set_check_cancellable()`][Self::set_check_cancellable()] to turn off that
151 /// behavior.) On the other hand, [`run_in_thread()`][Self::run_in_thread()]
152 /// guarantees that it will always run your
153 /// `task_func`, even if the task’s [`Cancellable`][crate::Cancellable]
154 /// is already cancelled before the task gets a chance to run;
155 /// you can start your `task_func` with a
156 /// [`return_error_if_cancelled()`][Self::return_error_if_cancelled()] check if you need the
157 /// old behavior.
158 /// - The ‘return’ methods (eg, [`return_pointer()`][Self::return_pointer()])
159 /// automatically cause the task to be ‘completed’ as well, and
160 /// there is no need to worry about the ‘complete’ vs ‘complete in idle’
161 /// distinction. (`GTask` automatically figures out
162 /// whether the task’s callback can be invoked directly, or
163 /// if it needs to be sent to another [`glib::MainContext`][crate::glib::MainContext], or delayed
164 /// until the next iteration of the current [`glib::MainContext`][crate::glib::MainContext].)
165 /// - The ‘finish’ functions for `GTask` based operations are generally
166 /// much simpler than `Gio::SimpleAsyncResult` ones, normally consisting
167 /// of only a single call to [`propagate_pointer()`][Self::propagate_pointer()] or the like.
168 /// Since [`propagate_pointer()`][Self::propagate_pointer()] ‘steals’ the return value from
169 /// the `GTask`, it is not necessary to juggle pointers around to
170 /// prevent it from being freed twice.
171 /// - With `Gio::SimpleAsyncResult`, it was common to call
172 /// `Gio::SimpleAsyncResult::propagate_error()` from the
173 /// `_finish()` wrapper function, and have
174 /// virtual method implementations only deal with successful
175 /// returns. This behavior is deprecated, because it makes it
176 /// difficult for a subclass to chain to a parent class’s async
177 /// methods. Instead, the wrapper function should just be a
178 /// simple wrapper, and the virtual method should call an
179 /// appropriate `g_task_propagate_` function.
180 /// Note that wrapper methods can now use
181 /// [`AsyncResultExt::legacy_propagate_error()`][crate::prelude::AsyncResultExt::legacy_propagate_error()] to do old-style
182 /// `Gio::SimpleAsyncResult` error-returning behavior, and
183 /// `Gio::AsyncResult::is_tagged()` to check if a result is tagged as
184 /// having come from the `_async()` wrapper
185 /// function (for ‘short-circuit’ results, such as when passing
186 /// `0` to [`InputStreamExtManual::read_async()`][crate::prelude::InputStreamExtManual::read_async()]).
187 ///
188 /// ## Thread-safety considerations
189 ///
190 /// Due to some infelicities in the API design, there is a
191 /// thread-safety concern that users of `GTask` have to be aware of:
192 ///
193 /// If the `main` thread drops its last reference to the source object
194 /// or the task data before the task is finalized, then the finalizers
195 /// of these objects may be called on the worker thread.
196 ///
197 /// This is a problem if the finalizers use non-threadsafe API, and
198 /// can lead to hard-to-debug crashes. Possible workarounds include:
199 ///
200 /// - Clear task data in a signal handler for `notify::completed`
201 /// - Keep iterating a main context in the main thread and defer
202 /// dropping the reference to the source object to that main
203 /// context when the task is finalized
204 ///
205 /// ## Properties
206 ///
207 ///
208 /// #### `completed`
209 /// Whether the task has completed, meaning its callback (if set) has been
210 /// invoked.
211 ///
212 /// This can only happen after g_task_return_pointer(),
213 /// g_task_return_error() or one of the other return functions have been called
214 /// on the task. However, it is not guaranteed to happen immediately after
215 /// those functions are called, as the task’s callback may need to be scheduled
216 /// to run in a different thread.
217 ///
218 /// That means it is **not safe** to use this property to track whether a
219 /// return function has been called on the #GTask. Callers must do that
220 /// tracking themselves, typically by linking the lifetime of the #GTask to the
221 /// control flow of their code.
222 ///
223 /// This property is guaranteed to change from [`false`] to [`true`] exactly once.
224 ///
225 /// The #GObject::notify signal for this change is emitted in the same main
226 /// context as the task’s callback, immediately after that callback is invoked.
227 ///
228 /// Readable
229 ///
230 /// # Implements
231 ///
232 /// [`trait@glib::ObjectExt`], [`AsyncResultExt`][trait@crate::prelude::AsyncResultExt]
233 #[doc(alias = "GTask")]
234 pub struct Task<V: ValueType + Send>(Object<ffi::GTask, ffi::GTaskClass>) @implements AsyncResult;
235
236 match fn {
237 type_ => || ffi::g_task_get_type(),
238 }
239}
240
241macro_rules! task_impl {
242 ($name:ident $(, @bound: $bound:tt)? $(, @safety: $safety:tt)?) => {
243 impl <V: Into<glib::Value> + ValueType $(+ $bound)?> $name<V> {
244 #[doc(alias = "g_task_new")]
245 #[allow(unused_unsafe)]
246 pub unsafe fn new<S, P, Q>(
247 source_object: Option<&S>,
248 cancellable: Option<&P>,
249 callback: Q,
250 ) -> Self
251 where
252 S: IsA<glib::Object> $(+ $bound)?,
253 P: IsA<Cancellable>,
254 Q: FnOnce($name<V>, Option<&S>) $(+ $bound)? + 'static,
255 {
256 let callback_data = Box_::new(callback);
257 unsafe extern "C" fn trampoline<
258 S: IsA<glib::Object> $(+ $bound)?,
259 V: ValueType $(+ $bound)?,
260 Q: FnOnce($name<V>, Option<&S>) $(+ $bound)? + 'static,
261 >(
262 source_object: *mut glib::gobject_ffi::GObject,
263 res: *mut ffi::GAsyncResult,
264 user_data: glib::ffi::gpointer,
265 ) { unsafe {
266 let callback: Box_<Q> = Box::from_raw(user_data as *mut _);
267 let task = AsyncResult::from_glib_none(res)
268 .downcast::<$name<V>>()
269 .unwrap();
270 let source_object = Option::<glib::Object>::from_glib_borrow(source_object);
271 callback(
272 task,
273 source_object.as_ref().as_ref().map(|s| s.unsafe_cast_ref()),
274 );
275 }}
276 let callback = trampoline::<S, V, Q>;
277 unsafe {
278 from_glib_full(ffi::g_task_new(
279 source_object.map(|p| p.as_ref()).to_glib_none().0,
280 cancellable.map(|p| p.as_ref()).to_glib_none().0,
281 Some(callback),
282 Box_::into_raw(callback_data) as *mut _,
283 ))
284 }
285 }
286
287 #[doc(alias = "g_task_get_cancellable")]
288 #[doc(alias = "get_cancellable")]
289 pub fn cancellable(&self) -> Option<Cancellable> {
290 unsafe { from_glib_none(ffi::g_task_get_cancellable(self.to_glib_none().0)) }
291 }
292
293 #[doc(alias = "g_task_get_check_cancellable")]
294 #[doc(alias = "get_check_cancellable")]
295 pub fn is_check_cancellable(&self) -> bool {
296 unsafe { from_glib(ffi::g_task_get_check_cancellable(self.to_glib_none().0)) }
297 }
298
299 #[doc(alias = "g_task_set_check_cancellable")]
300 pub fn set_check_cancellable(&self, check_cancellable: bool) {
301 unsafe {
302 ffi::g_task_set_check_cancellable(self.to_glib_none().0, check_cancellable.into_glib());
303 }
304 }
305
306 #[cfg(feature = "v2_60")]
307 #[cfg_attr(docsrs, doc(cfg(feature = "v2_60")))]
308 #[doc(alias = "g_task_set_name")]
309 pub fn set_name(&self, name: Option<&str>) {
310 unsafe {
311 ffi::g_task_set_name(self.to_glib_none().0, name.to_glib_none().0);
312 }
313 }
314
315 #[doc(alias = "g_task_set_return_on_cancel")]
316 pub fn set_return_on_cancel(&self, return_on_cancel: bool) -> bool {
317 unsafe {
318 from_glib(ffi::g_task_set_return_on_cancel(
319 self.to_glib_none().0,
320 return_on_cancel.into_glib(),
321 ))
322 }
323 }
324
325 #[doc(alias = "g_task_is_valid")]
326 pub fn is_valid(
327 result: &impl IsA<AsyncResult>,
328 source_object: Option<&impl IsA<glib::Object>>,
329 ) -> bool {
330 unsafe {
331 from_glib(ffi::g_task_is_valid(
332 result.as_ref().to_glib_none().0,
333 source_object.map(|p| p.as_ref()).to_glib_none().0,
334 ))
335 }
336 }
337
338 #[doc(alias = "get_priority")]
339 #[doc(alias = "g_task_get_priority")]
340 pub fn priority(&self) -> glib::source::Priority {
341 unsafe { FromGlib::from_glib(ffi::g_task_get_priority(self.to_glib_none().0)) }
342 }
343
344 #[doc(alias = "g_task_set_priority")]
345 pub fn set_priority(&self, priority: glib::source::Priority) {
346 unsafe {
347 ffi::g_task_set_priority(self.to_glib_none().0, priority.into_glib());
348 }
349 }
350
351 #[doc(alias = "g_task_get_completed")]
352 #[doc(alias = "get_completed")]
353 pub fn is_completed(&self) -> bool {
354 unsafe { from_glib(ffi::g_task_get_completed(self.to_glib_none().0)) }
355 }
356
357 #[doc(alias = "g_task_get_context")]
358 #[doc(alias = "get_context")]
359 pub fn context(&self) -> glib::MainContext {
360 unsafe { from_glib_none(ffi::g_task_get_context(self.to_glib_none().0)) }
361 }
362
363 #[cfg(feature = "v2_60")]
364 #[cfg_attr(docsrs, doc(cfg(feature = "v2_60")))]
365 #[doc(alias = "g_task_get_name")]
366 #[doc(alias = "get_name")]
367 pub fn name(&self) -> Option<glib::GString> {
368 unsafe { from_glib_none(ffi::g_task_get_name(self.to_glib_none().0)) }
369 }
370
371 #[doc(alias = "g_task_get_return_on_cancel")]
372 #[doc(alias = "get_return_on_cancel")]
373 pub fn is_return_on_cancel(&self) -> bool {
374 unsafe { from_glib(ffi::g_task_get_return_on_cancel(self.to_glib_none().0)) }
375 }
376
377 #[doc(alias = "g_task_had_error")]
378 pub fn had_error(&self) -> bool {
379 unsafe { from_glib(ffi::g_task_had_error(self.to_glib_none().0)) }
380 }
381
382 #[doc(alias = "completed")]
383 pub fn connect_completed_notify<F>(&self, f: F) -> SignalHandlerId
384 where
385 F: Fn(&$name<V>) $(+ $bound)? + 'static,
386 {
387 unsafe extern "C" fn notify_completed_trampoline<V, F>(
388 this: *mut ffi::GTask,
389 _param_spec: glib::ffi::gpointer,
390 f: glib::ffi::gpointer,
391 ) where
392 V: ValueType $(+ $bound)?,
393 F: Fn(&$name<V>) + 'static,
394 { unsafe {
395 let f: &F = &*(f as *const F);
396 f(&from_glib_borrow(this))
397 }}
398 unsafe {
399 let f: Box_<F> = Box_::new(f);
400 connect_raw(
401 self.as_ptr() as *mut _,
402 b"notify::completed\0".as_ptr() as *const _,
403 Some(transmute::<*const (), unsafe extern "C" fn()>(
404 notify_completed_trampoline::<V, F> as *const (),
405 )),
406 Box_::into_raw(f),
407 )
408 }
409 }
410
411 // the following functions are marked unsafe since they cannot be called
412 // more than once, but we have no way to enforce that since the task can be cloned
413
414 #[doc(alias = "g_task_return_error_if_cancelled")]
415 #[allow(unused_unsafe)]
416 pub $($safety)? fn return_error_if_cancelled(&self) -> bool {
417 unsafe { from_glib(ffi::g_task_return_error_if_cancelled(self.to_glib_none().0)) }
418 }
419
420 // rustdoc-stripper-ignore-next
421 /// Set the result of the task
422 ///
423 /// # Safety
424 ///
425 /// The value must be read with [`Task::propagate`],
426 /// `g_task_propagate_value` or `g_task_propagate_pointer`.
427 #[doc(alias = "g_task_return_value")]
428 #[doc(alias = "g_task_return_pointer")]
429 #[doc(alias = "g_task_return_error")]
430 #[allow(unused_unsafe)]
431 pub $($safety)? fn return_result(self, result: Result<V, glib::Error>) {
432 #[cfg(not(feature = "v2_64"))]
433 unsafe extern "C" fn value_free(value: *mut libc::c_void) { unsafe {
434 let _: glib::Value = from_glib_full(value as *mut glib::gobject_ffi::GValue);
435 }}
436
437 match result {
438 #[cfg(feature = "v2_64")]
439 Ok(v) => unsafe {
440 ffi::g_task_return_value(
441 self.to_glib_none().0,
442 v.to_value().to_glib_none().0 as *mut _,
443 )
444 },
445 #[cfg(not(feature = "v2_64"))]
446 Ok(v) => unsafe {
447 let v: glib::Value = v.into();
448 ffi::g_task_return_pointer(
449 self.to_glib_none().0,
450 <glib::Value as glib::translate::IntoGlibPtr::<*mut glib::gobject_ffi::GValue>>::into_glib_ptr(v) as glib::ffi::gpointer,
451 Some(value_free),
452 )
453 },
454 Err(e) => unsafe {
455 ffi::g_task_return_error(self.to_glib_none().0, e.into_glib_ptr());
456 },
457 }
458 }
459
460 // rustdoc-stripper-ignore-next
461 /// Set the result of the task as a boolean
462 ///
463 /// # Safety
464 ///
465 /// The value must be read with [`Task::propagate_boolean`],
466 /// or `g_task_propagate_boolean`.
467 #[doc(alias = "g_task_return_boolean")]
468 #[allow(unused_unsafe)]
469 pub $($safety)? fn return_boolean_result(self, result: Result<bool, glib::Error>) {
470 match result {
471 Ok(v) => unsafe { ffi::g_task_return_boolean(self.to_glib_none().0, v as i32) },
472 Err(e) => unsafe { ffi::g_task_return_error(self.to_glib_none().0, e.into_glib_ptr()) },
473 }
474 }
475
476 // rustdoc-stripper-ignore-next
477 /// Set the result of the task as an int
478 ///
479 /// # Safety
480 ///
481 /// The value must be read with [`Task::propagate_int`],
482 /// or `g_task_propagate_int`.
483 #[doc(alias = "g_task_return_int")]
484 #[allow(unused_unsafe)]
485 pub $($safety)? fn return_int_result(self, result: Result<isize, glib::Error>) {
486 match result {
487 Ok(v) => unsafe { ffi::g_task_return_int(self.to_glib_none().0, v) },
488 Err(e) => unsafe { ffi::g_task_return_error(self.to_glib_none().0, e.into_glib_ptr()) },
489 }
490 }
491
492
493 // rustdoc-stripper-ignore-next
494 /// Gets the result of the task and transfers ownership of it
495 ///
496 /// # Safety
497 ///
498 /// This must only be called once, and only if the result was set
499 /// via [`Task::return_result`], `g_task_return_value` or
500 /// `g_task_return_pointer`.
501 #[doc(alias = "g_task_propagate_value")]
502 #[doc(alias = "g_task_propagate_pointer")]
503 #[allow(unused_unsafe)]
504 pub unsafe fn propagate(self) -> Result<V, glib::Error> {
505 let mut error = ptr::null_mut();
506
507 unsafe {
508 #[cfg(feature = "v2_64")]
509 {
510 let mut value = glib::Value::uninitialized();
511 ffi::g_task_propagate_value(
512 self.to_glib_none().0,
513 value.to_glib_none_mut().0,
514 &mut error,
515 );
516
517 if error.is_null() {
518 Ok(V::from_value(&value))
519 } else {
520 Err(from_glib_full(error))
521 }
522 }
523
524 #[cfg(not(feature = "v2_64"))]
525 {
526 let value = ffi::g_task_propagate_pointer(self.to_glib_none().0, &mut error);
527
528 if error.is_null() {
529 let value = Option::<glib::Value>::from_glib_full(
530 value as *mut glib::gobject_ffi::GValue,
531 )
532 .expect("Task::propagate() called before Task::return_result()");
533 Ok(V::from_value(&value))
534 } else {
535 Err(from_glib_full(error))
536 }
537 }
538 }
539 }
540
541 // rustdoc-stripper-ignore-next
542 /// Gets the result of the task as a boolean, or the error
543 ///
544 /// # Safety
545 ///
546 /// This must only be called once, and only if the result was set
547 /// via [`Task::return_boolean_result`], or `g_task_return_boolean`.
548 #[doc(alias = "g_task_propagate_boolean")]
549 #[allow(unused_unsafe)]
550 pub unsafe fn propagate_boolean(self) -> Result<bool, glib::Error> {
551 let mut error = ptr::null_mut();
552
553 unsafe {
554 let res = ffi::g_task_propagate_boolean(self.to_glib_none().0, &mut error);
555
556 if error.is_null() {
557 Ok(res != 0)
558 } else {
559 Err(from_glib_full(error))
560 }
561 }
562 }
563
564 // rustdoc-stripper-ignore-next
565 /// Gets the result of the task as an int, or the error
566 ///
567 /// # Safety
568 ///
569 /// This must only be called once, and only if the result was set
570 /// via [`Task::return_int_result`], or `g_task_return_int`.
571 #[doc(alias = "g_task_propagate_int")]
572 #[allow(unused_unsafe)]
573 pub unsafe fn propagate_int(self) -> Result<isize, glib::Error> {
574 let mut error = ptr::null_mut();
575
576 unsafe {
577 let res = ffi::g_task_propagate_int(self.to_glib_none().0, &mut error);
578
579 if error.is_null() {
580 Ok(res)
581 } else {
582 Err(from_glib_full(error))
583 }
584 }
585 }
586 }
587 }
588}
589
590task_impl!(LocalTask);
591task_impl!(Task, @bound: Send, @safety: unsafe);
592
593impl<V: ValueType + Send> Task<V> {
594 #[doc(alias = "g_task_run_in_thread")]
595 pub fn run_in_thread<S, Q>(&self, task_func: Q)
596 where
597 S: IsA<glib::Object> + Send,
598 Q: FnOnce(Self, Option<&S>, Option<&Cancellable>) + Send + 'static,
599 {
600 let task_func_data = Box_::new(task_func);
601
602 // We store the func pointer into the task data.
603 // We intentionally do not expose a way to set the task data in the bindings.
604 // If we detect that the task data is set, there is not much we can do, so we panic.
605 unsafe {
606 assert!(
607 ffi::g_task_get_task_data(self.to_glib_none().0).is_null(),
608 "Task data was manually set or the task was run thread multiple times"
609 );
610
611 ffi::g_task_set_task_data(
612 self.to_glib_none().0,
613 Box_::into_raw(task_func_data) as *mut _,
614 None,
615 );
616 }
617
618 unsafe extern "C" fn trampoline<V, S, Q>(
619 task: *mut ffi::GTask,
620 source_object: *mut glib::gobject_ffi::GObject,
621 user_data: glib::ffi::gpointer,
622 cancellable: *mut ffi::GCancellable,
623 ) where
624 V: ValueType + Send,
625 S: IsA<glib::Object> + Send,
626 Q: FnOnce(Task<V>, Option<&S>, Option<&Cancellable>) + Send + 'static,
627 {
628 unsafe {
629 let task = Task::from_glib_none(task);
630 let source_object = Option::<glib::Object>::from_glib_borrow(source_object);
631 let cancellable = Option::<Cancellable>::from_glib_borrow(cancellable);
632 let task_func: Box_<Q> = Box::from_raw(user_data as *mut _);
633 task_func(
634 task,
635 source_object.as_ref().as_ref().map(|s| s.unsafe_cast_ref()),
636 cancellable.as_ref().as_ref(),
637 );
638 }
639 }
640
641 let task_func = trampoline::<V, S, Q>;
642 unsafe {
643 ffi::g_task_run_in_thread(self.to_glib_none().0, Some(task_func));
644 }
645 }
646}
647
648unsafe impl<V: ValueType + Send> Send for Task<V> {}
649unsafe impl<V: ValueType + Send> Sync for Task<V> {}
650
651// rustdoc-stripper-ignore-next
652/// A handle to a task running on the I/O thread pool.
653///
654/// Like [`std::thread::JoinHandle`] for a blocking I/O task rather than a thread. The return value
655/// from the task can be retrieved by awaiting on this handle. Dropping the handle "detaches" the
656/// task, allowing it to complete but discarding the return value.
657#[derive(Debug)]
658pub struct JoinHandle<T> {
659 rx: oneshot::Receiver<std::thread::Result<T>>,
660}
661
662impl<T> JoinHandle<T> {
663 #[inline]
664 fn new() -> (Self, oneshot::Sender<std::thread::Result<T>>) {
665 let (tx, rx) = oneshot::channel();
666 (Self { rx }, tx)
667 }
668}
669
670impl<T> Future for JoinHandle<T> {
671 type Output = std::thread::Result<T>;
672 #[inline]
673 fn poll(
674 mut self: std::pin::Pin<&mut Self>,
675 cx: &mut std::task::Context<'_>,
676 ) -> std::task::Poll<Self::Output> {
677 std::pin::Pin::new(&mut self.rx)
678 .poll(cx)
679 .map(|r| r.unwrap())
680 }
681}
682
683impl<T> futures_core::FusedFuture for JoinHandle<T> {
684 #[inline]
685 fn is_terminated(&self) -> bool {
686 self.rx.is_terminated()
687 }
688}
689
690// rustdoc-stripper-ignore-next
691/// Runs a blocking I/O task on the I/O thread pool.
692///
693/// Calls `func` on the internal Gio thread pool for blocking I/O operations. The thread pool is
694/// shared with other Gio async I/O operations, and may rate-limit the tasks it receives. Callers
695/// may want to avoid blocking indefinitely by making sure blocking calls eventually time out.
696///
697/// This function should not be used to spawn async tasks. Instead, use
698/// [`glib::MainContext::spawn`] or [`glib::MainContext::spawn_local`] to run a future.
699pub fn spawn_blocking<T, F>(func: F) -> JoinHandle<T>
700where
701 T: Send + 'static,
702 F: FnOnce() -> T + Send + 'static,
703{
704 unsafe extern "C" fn free_box<T: Send + 'static>(ptr: glib::ffi::gpointer) {
705 unsafe {
706 let _ = Box::from_raw(ptr as *mut std::thread::Result<T>);
707 }
708 }
709
710 let (join, tx) = JoinHandle::new();
711
712 // use Cancellable::NONE as source obj to fulfill `Send` requirement
713 let task = unsafe {
714 Task::<bool>::new(Cancellable::NONE, Cancellable::NONE, move |task, _| {
715 let mut err = ptr::null_mut();
716 let ptr = ffi::g_task_propagate_pointer(task.to_glib_none().0, &mut err);
717
718 let res = *Box::from_raw(ptr as *mut std::thread::Result<T>);
719 let _ = tx.send(res);
720 })
721 };
722 task.run_in_thread(move |task, _: Option<&Cancellable>, _| {
723 let res = panic::catch_unwind(panic::AssertUnwindSafe(func));
724 let tx = Box::new(res);
725
726 unsafe {
727 ffi::g_task_return_pointer(
728 task.to_glib_none().0,
729 Box::into_raw(tx) as glib::ffi::gpointer,
730 Some(free_box::<T>),
731 )
732 }
733 });
734
735 join
736}
737
738#[cfg(test)]
739mod test {
740 use super::*;
741 use crate::{prelude::*, test_util::run_async_local};
742
743 #[test]
744 fn test_int_value_async_result() {
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 task.return_result(Ok(100_i32));
758 });
759
760 match fut {
761 Err(_) => panic!(),
762 Ok(i) => assert_eq!(i, 100),
763 }
764 }
765
766 #[test]
767 fn test_boolean_async_result() {
768 let fut = run_async_local(|tx, l| {
769 let cancellable = crate::Cancellable::new();
770 let task = unsafe {
771 crate::LocalTask::new(
772 None,
773 Some(&cancellable),
774 move |t: LocalTask<bool>, _b: Option<&glib::Object>| {
775 tx.send(t.propagate_boolean()).unwrap();
776 l.quit();
777 },
778 )
779 };
780 task.return_boolean_result(Ok(true));
781 });
782
783 match fut {
784 Err(_) => panic!(),
785 Ok(i) => assert!(i),
786 }
787 }
788
789 #[test]
790 fn test_int_async_result() {
791 let fut = run_async_local(|tx, l| {
792 let cancellable = crate::Cancellable::new();
793 let task = unsafe {
794 crate::LocalTask::new(
795 None,
796 Some(&cancellable),
797 move |t: LocalTask<i32>, _b: Option<&glib::Object>| {
798 tx.send(t.propagate_int()).unwrap();
799 l.quit();
800 },
801 )
802 };
803 task.return_int_result(Ok(100_isize));
804 });
805
806 match fut {
807 Err(_) => panic!(),
808 Ok(i) => assert_eq!(i, 100),
809 }
810 }
811
812 #[test]
813 fn test_object_async_result() {
814 use glib::subclass::prelude::*;
815 pub struct MySimpleObjectPrivate {
816 pub size: std::cell::RefCell<Option<i64>>,
817 }
818
819 #[glib::object_subclass]
820 impl ObjectSubclass for MySimpleObjectPrivate {
821 const NAME: &'static str = "MySimpleObjectPrivate";
822 type Type = MySimpleObject;
823
824 fn new() -> Self {
825 Self {
826 size: std::cell::RefCell::new(Some(100)),
827 }
828 }
829 }
830
831 impl ObjectImpl for MySimpleObjectPrivate {}
832
833 glib::wrapper! {
834 pub struct MySimpleObject(ObjectSubclass<MySimpleObjectPrivate>);
835 }
836
837 impl MySimpleObject {
838 pub fn new() -> Self {
839 glib::Object::new()
840 }
841
842 #[doc(alias = "get_size")]
843 pub fn size(&self) -> Option<i64> {
844 *self.imp().size.borrow()
845 }
846
847 pub fn set_size(&self, size: i64) {
848 self.imp().size.borrow_mut().replace(size);
849 }
850 }
851
852 impl Default for MySimpleObject {
853 fn default() -> Self {
854 Self::new()
855 }
856 }
857
858 let fut = run_async_local(|tx, l| {
859 let cancellable = crate::Cancellable::new();
860 let task = unsafe {
861 crate::LocalTask::new(
862 None,
863 Some(&cancellable),
864 move |t: LocalTask<glib::Object>, _b: Option<&glib::Object>| {
865 tx.send(t.propagate()).unwrap();
866 l.quit();
867 },
868 )
869 };
870 let my_object = MySimpleObject::new();
871 my_object.set_size(100);
872 task.return_result(Ok(my_object.upcast::<glib::Object>()));
873 });
874
875 match fut {
876 Err(_) => panic!(),
877 Ok(o) => {
878 let o = o.downcast::<MySimpleObject>().unwrap();
879 assert_eq!(o.size(), Some(100));
880 }
881 }
882 }
883
884 #[test]
885 fn test_error() {
886 let fut = run_async_local(|tx, l| {
887 let cancellable = crate::Cancellable::new();
888 let task = unsafe {
889 crate::LocalTask::new(
890 None,
891 Some(&cancellable),
892 move |t: LocalTask<i32>, _b: Option<&glib::Object>| {
893 tx.send(t.propagate()).unwrap();
894 l.quit();
895 },
896 )
897 };
898 task.return_result(Err(glib::Error::new(
899 crate::IOErrorEnum::WouldBlock,
900 "WouldBlock",
901 )));
902 });
903
904 match fut {
905 Err(e) => match e.kind().unwrap() {
906 crate::IOErrorEnum::WouldBlock => {}
907 _ => panic!(),
908 },
909 Ok(_) => panic!(),
910 }
911 }
912
913 #[test]
914 fn test_cancelled() {
915 let fut = run_async_local(|tx, l| {
916 let cancellable = crate::Cancellable::new();
917 let task = unsafe {
918 crate::LocalTask::new(
919 None,
920 Some(&cancellable),
921 move |t: LocalTask<i32>, _b: Option<&glib::Object>| {
922 tx.send(t.propagate()).unwrap();
923 l.quit();
924 },
925 )
926 };
927 cancellable.cancel();
928 task.return_error_if_cancelled();
929 });
930
931 match fut {
932 Err(e) => match e.kind().unwrap() {
933 crate::IOErrorEnum::Cancelled => {}
934 _ => panic!(),
935 },
936 Ok(_) => panic!(),
937 }
938 }
939
940 #[test]
941 fn test_spawn_blocking() {
942 let main_context = glib::MainContext::new();
943 main_context.block_on(async {
944 let x = super::spawn_blocking(|| 123).await;
945 assert_eq!(x.unwrap(), 123);
946 });
947 }
948}