Skip to main content

glib/
main_context.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::mem;
4
5use crate::ffi::{self, gboolean, gpointer};
6
7use crate::{MainContext, Source, SourceId, source::Priority, translate::*};
8
9impl MainContext {
10    /// Prepares to poll sources within a main loop.
11    ///
12    /// The resulting information
13    /// for polling is determined by calling `GLib::MainContext::query()`.
14    ///
15    /// You must have successfully acquired the context with
16    /// [`acquire()`][Self::acquire()] before you may call this function.
17    ///
18    /// # Returns
19    ///
20    /// true if some source is ready to be dispatched prior to polling,
21    ///   false otherwise
22    ///
23    /// ## `priority`
24    /// location to store priority of highest priority
25    ///   source already ready
26    #[doc(alias = "g_main_context_prepare")]
27    pub fn prepare(&self) -> (bool, i32) {
28        unsafe {
29            let mut priority = mem::MaybeUninit::uninit();
30
31            let res = from_glib(ffi::g_main_context_prepare(
32                self.to_glib_none().0,
33                priority.as_mut_ptr(),
34            ));
35            let priority = priority.assume_init();
36            (res, priority)
37        }
38    }
39
40    /// Finds a [`Source`][crate::Source] given a pair of context and ID.
41    ///
42    /// It is a programmer error to attempt to look up a non-existent source.
43    ///
44    /// More specifically: source IDs can be reissued after a source has been
45    /// destroyed and therefore it is never valid to use this function with a
46    /// source ID which may have already been removed.  An example is when
47    /// scheduling an idle to run in another thread with `idle_add()`: the
48    /// idle may already have run and been removed by the time this function
49    /// is called on its (now invalid) source ID.  This source ID may have
50    /// been reissued, leading to the operation being performed against the
51    /// wrong source.
52    /// ## `source_id`
53    /// the source ID, as returned by `GLib::Source::get_id()`
54    ///
55    /// # Returns
56    ///
57    /// the source
58    #[doc(alias = "g_main_context_find_source_by_id")]
59    pub fn find_source_by_id(&self, source_id: &SourceId) -> Option<Source> {
60        unsafe {
61            from_glib_none(ffi::g_main_context_find_source_by_id(
62                self.to_glib_none().0,
63                source_id.as_raw(),
64            ))
65        }
66    }
67
68    // rustdoc-stripper-ignore-next
69    /// Invokes `func` on the main context.
70    ///
71    /// If the current thread is the owner of the main context or the main context currently has no
72    /// owner then `func` will be called directly from inside this function. If this behaviour is
73    /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`]
74    /// [`glib::idle_add`](crate::idle_add) instead.
75    // rustdoc-stripper-ignore-next-stop
76    /// Invokes a function in such a way that @self is owned during the
77    /// invocation of @function.
78    ///
79    /// If @self is `NULL` then the global-default main context — as
80    /// returned by [`default()`][Self::default()] — is used.
81    ///
82    /// If @self is owned by the current thread, @function is called
83    /// directly.  Otherwise, if @self is the thread-default main context
84    /// of the current thread and [`acquire()`][Self::acquire()] succeeds,
85    /// then @function is called and [`release()`][Self::release()] is called
86    /// afterwards.
87    ///
88    /// In any other case, an idle source is created to call @function and
89    /// that source is attached to @self (presumably to be run in another
90    /// thread).  The idle source is attached with `GLib::PRIORITY_DEFAULT`
91    /// priority.  If you want a different priority, use
92    /// [`invoke_full()`][Self::invoke_full()].
93    ///
94    /// Note that, as with normal idle functions, @function should probably return
95    /// `GLib::SOURCE_REMOVE`.  If it returns `GLib::SOURCE_CONTINUE`, it
96    /// will be continuously run in a loop (and may prevent this call from returning).
97    /// ## `function`
98    /// function to call
99    #[doc(alias = "g_main_context_invoke")]
100    pub fn invoke<F>(&self, func: F)
101    where
102        F: FnOnce() + Send + 'static,
103    {
104        self.invoke_with_priority(crate::Priority::DEFAULT_IDLE, func);
105    }
106
107    // rustdoc-stripper-ignore-next
108    /// Invokes `func` on the main context with the given priority.
109    ///
110    /// If the current thread is the owner of the main context or the main context currently has no
111    /// owner then `func` will be called directly from inside this function. If this behaviour is
112    /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`]
113    /// [`glib::idle_add`](crate::idle_add) instead.
114    #[doc(alias = "g_main_context_invoke_full")]
115    pub fn invoke_with_priority<F>(&self, priority: Priority, func: F)
116    where
117        F: FnOnce() + Send + 'static,
118    {
119        unsafe {
120            self.invoke_unsafe(priority, func);
121        }
122    }
123
124    // rustdoc-stripper-ignore-next
125    /// Invokes `func` on the main context.
126    ///
127    /// Different to `invoke()`, this does not require `func` to be
128    /// `Send` but can only be called from the thread that owns the main context.
129    ///
130    /// This function panics if called from a different thread than the one that
131    /// owns the main context.
132    ///
133    /// Note that this effectively means that `func` is called directly from inside this function
134    /// or otherwise panics immediately. If this behaviour is not desired and `func` should always
135    /// be called asynchronously then use [`MainContext::spawn_local`]
136    /// [`glib::idle_add_local`](crate::idle_add_local) instead.
137    pub fn invoke_local<F>(&self, func: F)
138    where
139        F: FnOnce() + 'static,
140    {
141        self.invoke_local_with_priority(crate::Priority::DEFAULT_IDLE, func);
142    }
143
144    // rustdoc-stripper-ignore-next
145    /// Invokes `func` on the main context with the given priority.
146    ///
147    /// Different to `invoke_with_priority()`, this does not require `func` to be
148    /// `Send` but can only be called from the thread that owns the main context.
149    ///
150    /// This function panics if called from a different thread than the one that
151    /// owns the main context.
152    ///
153    /// Note that this effectively means that `func` is called directly from inside this function
154    /// or otherwise panics immediately. If this behaviour is not desired and `func` should always
155    /// be called asynchronously then use [`MainContext::spawn_local`]
156    /// [`glib::idle_add_local`](crate::idle_add_local) instead.
157    #[allow(clippy::if_same_then_else)]
158    pub fn invoke_local_with_priority<F>(&self, _priority: Priority, func: F)
159    where
160        F: FnOnce() + 'static,
161    {
162        // Checks from `g_main_context_invoke_full()`
163        // FIXME: Combine the first two cases somehow
164        if self.is_owner() {
165            func();
166        } else {
167            match self.acquire() {
168                Ok(_acquire) => {
169                    func();
170                }
171                _ => {
172                    panic!("Must be called from a thread that owns the main context");
173                }
174            }
175        }
176    }
177
178    unsafe fn invoke_unsafe<F>(&self, priority: Priority, func: F)
179    where
180        F: FnOnce() + 'static,
181    {
182        unsafe {
183            unsafe extern "C" fn trampoline<F: FnOnce() + 'static>(func: gpointer) -> gboolean {
184                unsafe {
185                    let func: &mut Option<F> = &mut *(func as *mut Option<F>);
186                    let func = func
187                        .take()
188                        .expect("MainContext::invoke() closure called multiple times");
189                    func();
190                    ffi::G_SOURCE_REMOVE
191                }
192            }
193            unsafe extern "C" fn destroy_closure<F: FnOnce() + 'static>(ptr: gpointer) {
194                unsafe {
195                    let _ = Box::<Option<F>>::from_raw(ptr as *mut _);
196                }
197            }
198            let func = Box::into_raw(Box::new(Some(func)));
199            ffi::g_main_context_invoke_full(
200                self.to_glib_none().0,
201                priority.into_glib(),
202                Some(trampoline::<F>),
203                func as gpointer,
204                Some(destroy_closure::<F>),
205            )
206        }
207    }
208
209    // rustdoc-stripper-ignore-next
210    /// Call closure with the main context configured as the thread default one.
211    ///
212    /// The thread default main context is changed in a panic-safe manner before calling `func` and
213    /// released again afterwards regardless of whether closure panicked or not.
214    ///
215    /// This will fail if the main context is owned already by another thread.
216    // rustdoc-stripper-ignore-next-stop
217    /// Acquires @self and sets it as the thread-default context for the
218    /// current thread. This will cause certain asynchronous operations
219    /// (such as most [Gio](../gio/index.html)-based I/O) which are
220    /// started in this thread to run under @self and deliver their
221    /// results to its main loop, rather than running under the global
222    /// default main context in the main thread. Note that calling this function
223    /// changes the context returned by [`thread_default()`][Self::thread_default()],
224    /// not the one returned by [`default()`][Self::default()], so it does not
225    /// affect the context used by functions like `idle_add()`.
226    ///
227    /// Normally you would call this function shortly after creating a new
228    /// thread, passing it a [`MainContext`][crate::MainContext] which will be run by a
229    /// [`MainLoop`][crate::MainLoop] in that thread, to set a new default context for all
230    /// async operations in that thread. In this case you may not need to
231    /// ever call [`pop_thread_default()`][Self::pop_thread_default()], assuming you want
232    /// the new [`MainContext`][crate::MainContext] to be the default for the whole lifecycle
233    /// of the thread.
234    ///
235    /// If you don’t have control over how the new thread was created (e.g.
236    /// in the new thread isn’t newly created, or if the thread life
237    /// cycle is managed by a #GThreadPool), it is always suggested to wrap
238    /// the logic that needs to use the new [`MainContext`][crate::MainContext] inside a
239    /// [`with_thread_default()`][Self::with_thread_default()] /
240    /// [`pop_thread_default()`][Self::pop_thread_default()] pair, otherwise threads that
241    /// are re-used will end up never explicitly releasing the
242    /// [`MainContext`][crate::MainContext] reference they hold.
243    ///
244    /// In some cases you may want to schedule a single operation in a
245    /// non-default context, or temporarily use a non-default context in
246    /// the main thread. In that case, you can wrap the call to the
247    /// asynchronous operation inside a
248    /// [`with_thread_default()`][Self::with_thread_default()] /
249    /// [`pop_thread_default()`][Self::pop_thread_default()] pair, but it is up to you to
250    /// ensure that no other asynchronous operations accidentally get
251    /// started while the non-default context is active.
252    ///
253    /// Beware that libraries that predate this function may not correctly
254    /// handle being used from a thread with a thread-default context. For example,
255    /// see `g_file_supports_thread_contexts()`.
256    #[doc(alias = "g_main_context_push_thread_default")]
257    pub fn with_thread_default<R, F: FnOnce() -> R + Sized>(
258        &self,
259        func: F,
260    ) -> Result<R, crate::BoolError> {
261        let _acquire = self.acquire()?;
262        let _thread_default = ThreadDefaultContext::new(self);
263        Ok(func())
264    }
265
266    // rustdoc-stripper-ignore-next
267    /// Acquire ownership of the main context.
268    ///
269    /// Ownership will automatically be released again once the returned acquire guard is dropped.
270    ///
271    /// This will fail if the main context is owned already by another thread.
272    // rustdoc-stripper-ignore-next-stop
273    /// Tries to become the owner of the specified context.
274    ///
275    /// If some other thread is the owner of the context,
276    /// returns false immediately. Ownership is properly
277    /// recursive: the owner can require ownership again
278    /// and will release ownership when [`release()`][Self::release()]
279    /// is called as many times as [`acquire()`][Self::acquire()].
280    ///
281    /// You must be the owner of a context before you
282    /// can call [`prepare()`][Self::prepare()], `GLib::MainContext::query()`,
283    /// `GLib::MainContext::check()`, [`dispatch()`][Self::dispatch()],
284    /// [`release()`][Self::release()].
285    ///
286    /// Since 2.76 @self can be `NULL` to use the global-default
287    /// main context.
288    ///
289    /// # Returns
290    ///
291    /// true if this thread is now the owner of @self, false otherwise
292    #[doc(alias = "g_main_context_acquire")]
293    pub fn acquire(&self) -> Result<MainContextAcquireGuard<'_>, crate::BoolError> {
294        unsafe {
295            let ret: bool = from_glib(ffi::g_main_context_acquire(self.to_glib_none().0));
296            if ret {
297                Ok(MainContextAcquireGuard(self))
298            } else {
299                Err(bool_error!(
300                    "Failed to acquire ownership of main context, already acquired by another thread"
301                ))
302            }
303        }
304    }
305}
306
307#[must_use = "if unused the main context will be released immediately"]
308pub struct MainContextAcquireGuard<'a>(&'a MainContext);
309
310impl Drop for MainContextAcquireGuard<'_> {
311    #[doc(alias = "g_main_context_release")]
312    #[inline]
313    fn drop(&mut self) {
314        unsafe {
315            ffi::g_main_context_release(self.0.to_glib_none().0);
316        }
317    }
318}
319
320struct ThreadDefaultContext<'a>(&'a MainContext);
321
322impl ThreadDefaultContext<'_> {
323    fn new(ctx: &MainContext) -> ThreadDefaultContext<'_> {
324        unsafe {
325            ffi::g_main_context_push_thread_default(ctx.to_glib_none().0);
326        }
327        ThreadDefaultContext(ctx)
328    }
329}
330
331impl Drop for ThreadDefaultContext<'_> {
332    #[inline]
333    fn drop(&mut self) {
334        unsafe {
335            ffi::g_main_context_pop_thread_default(self.0.to_glib_none().0);
336        }
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use std::{panic, ptr, thread};
343
344    use super::*;
345
346    #[test]
347    fn test_invoke() {
348        let c = MainContext::new();
349        let l = crate::MainLoop::new(Some(&c), false);
350
351        let l_clone = l.clone();
352        let join_handle = thread::spawn(move || {
353            c.invoke(move || l_clone.quit());
354        });
355
356        l.run();
357
358        join_handle.join().unwrap();
359    }
360
361    fn is_same_context(a: &MainContext, b: &MainContext) -> bool {
362        ptr::eq(a.to_glib_none().0, b.to_glib_none().0)
363    }
364
365    #[test]
366    fn test_with_thread_default() {
367        let a = MainContext::new();
368        let b = MainContext::new();
369
370        assert!(!is_same_context(&a, &b));
371
372        a.with_thread_default(|| {
373            let t = MainContext::thread_default().unwrap();
374            assert!(is_same_context(&a, &t));
375
376            b.with_thread_default(|| {
377                let t = MainContext::thread_default().unwrap();
378                assert!(is_same_context(&b, &t));
379            })
380            .unwrap();
381
382            let t = MainContext::thread_default().unwrap();
383            assert!(is_same_context(&a, &t));
384        })
385        .unwrap();
386    }
387
388    #[test]
389    fn test_with_thread_default_is_panic_safe() {
390        let a = MainContext::new();
391        let b = MainContext::new();
392
393        assert!(!is_same_context(&a, &b));
394
395        a.with_thread_default(|| {
396            let t = MainContext::thread_default().unwrap();
397            assert!(is_same_context(&a, &t));
398
399            let result = panic::catch_unwind(|| {
400                b.with_thread_default(|| {
401                    panic!();
402                })
403                .unwrap();
404            });
405            assert!(result.is_err());
406
407            let t = MainContext::thread_default().unwrap();
408            assert!(is_same_context(&a, &t));
409        })
410        .unwrap();
411    }
412}