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    ///  is used.
77    ///
78    /// If @self is owned by the current thread, @function is called
79    /// directly.  Otherwise, if @self is the thread-default main context
80    /// of the current thread and [`acquire()`][Self::acquire()] succeeds,
81    /// then @function is called and [`release()`][Self::release()] is called
82    /// afterwards.
83    ///
84    /// In any other case, an idle source is created to call @function and
85    /// that source is attached to @self (presumably to be run in another
86    /// thread).  The idle source is attached with `GLib::PRIORITY_DEFAULT`
87    /// priority.  If you want a different priority, use
88    /// [`invoke_full()`][Self::invoke_full()].
89    ///
90    /// Note that, as with normal idle functions, @function should probably return
91    /// `GLib::SOURCE_REMOVE`.  If it returns `GLib::SOURCE_CONTINUE`, it
92    /// will be continuously run in a loop (and may prevent this call from returning).
93    /// ## `function`
94    /// function to call
95    #[doc(alias = "g_main_context_invoke")]
96    pub fn invoke<F>(&self, func: F)
97    where
98        F: FnOnce() + Send + 'static,
99    {
100        self.invoke_with_priority(crate::Priority::DEFAULT_IDLE, func);
101    }
102
103    // rustdoc-stripper-ignore-next
104    /// Invokes `func` on the main context with the given priority.
105    ///
106    /// If the current thread is the owner of the main context or the main context currently has no
107    /// owner then `func` will be called directly from inside this function. If this behaviour is
108    /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`]
109    /// [`glib::idle_add`](crate::idle_add) instead.
110    #[doc(alias = "g_main_context_invoke_full")]
111    pub fn invoke_with_priority<F>(&self, priority: Priority, func: F)
112    where
113        F: FnOnce() + Send + 'static,
114    {
115        unsafe {
116            self.invoke_unsafe(priority, func);
117        }
118    }
119
120    // rustdoc-stripper-ignore-next
121    /// Invokes `func` on the main context.
122    ///
123    /// Different to `invoke()`, this does not require `func` to be
124    /// `Send` but can only be called from the thread that owns the main context.
125    ///
126    /// This function panics if called from a different thread than the one that
127    /// owns the main context.
128    ///
129    /// Note that this effectively means that `func` is called directly from inside this function
130    /// or otherwise panics immediately. If this behaviour is not desired and `func` should always
131    /// be called asynchronously then use [`MainContext::spawn_local`]
132    /// [`glib::idle_add_local`](crate::idle_add_local) instead.
133    pub fn invoke_local<F>(&self, func: F)
134    where
135        F: FnOnce() + 'static,
136    {
137        self.invoke_local_with_priority(crate::Priority::DEFAULT_IDLE, func);
138    }
139
140    // rustdoc-stripper-ignore-next
141    /// Invokes `func` on the main context with the given priority.
142    ///
143    /// Different to `invoke_with_priority()`, this does not require `func` to be
144    /// `Send` but can only be called from the thread that owns the main context.
145    ///
146    /// This function panics if called from a different thread than the one that
147    /// owns the main context.
148    ///
149    /// Note that this effectively means that `func` is called directly from inside this function
150    /// or otherwise panics immediately. If this behaviour is not desired and `func` should always
151    /// be called asynchronously then use [`MainContext::spawn_local`]
152    /// [`glib::idle_add_local`](crate::idle_add_local) instead.
153    #[allow(clippy::if_same_then_else)]
154    pub fn invoke_local_with_priority<F>(&self, _priority: Priority, func: F)
155    where
156        F: FnOnce() + 'static,
157    {
158        // Checks from `g_main_context_invoke_full()`
159        // FIXME: Combine the first two cases somehow
160        if self.is_owner() {
161            func();
162        } else {
163            match self.acquire() {
164                Ok(_acquire) => {
165                    func();
166                }
167                _ => {
168                    panic!("Must be called from a thread that owns the main context");
169                }
170            }
171        }
172    }
173
174    unsafe fn invoke_unsafe<F>(&self, priority: Priority, func: F)
175    where
176        F: FnOnce() + 'static,
177    {
178        unsafe {
179            unsafe extern "C" fn trampoline<F: FnOnce() + 'static>(func: gpointer) -> gboolean {
180                unsafe {
181                    let func: &mut Option<F> = &mut *(func as *mut Option<F>);
182                    let func = func
183                        .take()
184                        .expect("MainContext::invoke() closure called multiple times");
185                    func();
186                    ffi::G_SOURCE_REMOVE
187                }
188            }
189            unsafe extern "C" fn destroy_closure<F: FnOnce() + 'static>(ptr: gpointer) {
190                unsafe {
191                    let _ = Box::<Option<F>>::from_raw(ptr as *mut _);
192                }
193            }
194            let func = Box::into_raw(Box::new(Some(func)));
195            ffi::g_main_context_invoke_full(
196                self.to_glib_none().0,
197                priority.into_glib(),
198                Some(trampoline::<F>),
199                func as gpointer,
200                Some(destroy_closure::<F>),
201            )
202        }
203    }
204
205    // rustdoc-stripper-ignore-next
206    /// Call closure with the main context configured as the thread default one.
207    ///
208    /// The thread default main context is changed in a panic-safe manner before calling `func` and
209    /// released again afterwards regardless of whether closure panicked or not.
210    ///
211    /// This will fail if the main context is owned already by another thread.
212    // rustdoc-stripper-ignore-next-stop
213    /// t newly created, or if the thread life
214    /// cycle is managed by a #GThreadPool), it is always suggested to wrap
215    /// the logic that needs to use the new [`MainContext`][crate::MainContext] inside a
216    /// [`with_thread_default()`][Self::with_thread_default()] /
217    /// [`pop_thread_default()`][Self::pop_thread_default()] pair, otherwise threads that
218    /// are re-used will end up never explicitly releasing the
219    /// [`MainContext`][crate::MainContext] reference they hold.
220    ///
221    /// In some cases you may want to schedule a single operation in a
222    /// non-default context, or temporarily use a non-default context in
223    /// the main thread. In that case, you can wrap the call to the
224    /// asynchronous operation inside a
225    /// [`with_thread_default()`][Self::with_thread_default()] /
226    /// [`pop_thread_default()`][Self::pop_thread_default()] pair, but it is up to you to
227    /// ensure that no other asynchronous operations accidentally get
228    /// started while the non-default context is active.
229    ///
230    /// Beware that libraries that predate this function may not correctly
231    /// handle being used from a thread with a thread-default context. For example,
232    /// see `g_file_supports_thread_contexts()`.
233    #[doc(alias = "g_main_context_push_thread_default")]
234    pub fn with_thread_default<R, F: FnOnce() -> R + Sized>(
235        &self,
236        func: F,
237    ) -> Result<R, crate::BoolError> {
238        let _acquire = self.acquire()?;
239        let _thread_default = ThreadDefaultContext::new(self);
240        Ok(func())
241    }
242
243    // rustdoc-stripper-ignore-next
244    /// Acquire ownership of the main context.
245    ///
246    /// Ownership will automatically be released again once the returned acquire guard is dropped.
247    ///
248    /// This will fail if the main context is owned already by another thread.
249    // rustdoc-stripper-ignore-next-stop
250    /// Tries to become the owner of the specified context.
251    ///
252    /// If some other thread is the owner of the context,
253    /// returns false immediately. Ownership is properly
254    /// recursive: the owner can require ownership again
255    /// and will release ownership when [`release()`][Self::release()]
256    /// is called as many times as [`acquire()`][Self::acquire()].
257    ///
258    /// You must be the owner of a context before you
259    /// can call [`prepare()`][Self::prepare()], `GLib::MainContext::query()`,
260    /// `GLib::MainContext::check()`, [`dispatch()`][Self::dispatch()],
261    /// [`release()`][Self::release()].
262    ///
263    /// Since 2.76 @self can be `NULL` to use the global-default
264    /// main context.
265    ///
266    /// # Returns
267    ///
268    /// true if this thread is now the owner of @self, false otherwise
269    #[doc(alias = "g_main_context_acquire")]
270    pub fn acquire(&self) -> Result<MainContextAcquireGuard<'_>, crate::BoolError> {
271        unsafe {
272            let ret: bool = from_glib(ffi::g_main_context_acquire(self.to_glib_none().0));
273            if ret {
274                Ok(MainContextAcquireGuard(self))
275            } else {
276                Err(bool_error!(
277                    "Failed to acquire ownership of main context, already acquired by another thread"
278                ))
279            }
280        }
281    }
282}
283
284#[must_use = "if unused the main context will be released immediately"]
285pub struct MainContextAcquireGuard<'a>(&'a MainContext);
286
287impl Drop for MainContextAcquireGuard<'_> {
288    #[doc(alias = "g_main_context_release")]
289    #[inline]
290    fn drop(&mut self) {
291        unsafe {
292            ffi::g_main_context_release(self.0.to_glib_none().0);
293        }
294    }
295}
296
297struct ThreadDefaultContext<'a>(&'a MainContext);
298
299impl ThreadDefaultContext<'_> {
300    fn new(ctx: &MainContext) -> ThreadDefaultContext<'_> {
301        unsafe {
302            ffi::g_main_context_push_thread_default(ctx.to_glib_none().0);
303        }
304        ThreadDefaultContext(ctx)
305    }
306}
307
308impl Drop for ThreadDefaultContext<'_> {
309    #[inline]
310    fn drop(&mut self) {
311        unsafe {
312            ffi::g_main_context_pop_thread_default(self.0.to_glib_none().0);
313        }
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use std::{panic, ptr, thread};
320
321    use super::*;
322
323    #[test]
324    fn test_invoke() {
325        let c = MainContext::new();
326        let l = crate::MainLoop::new(Some(&c), false);
327
328        let l_clone = l.clone();
329        let join_handle = thread::spawn(move || {
330            c.invoke(move || l_clone.quit());
331        });
332
333        l.run();
334
335        join_handle.join().unwrap();
336    }
337
338    fn is_same_context(a: &MainContext, b: &MainContext) -> bool {
339        ptr::eq(a.to_glib_none().0, b.to_glib_none().0)
340    }
341
342    #[test]
343    fn test_with_thread_default() {
344        let a = MainContext::new();
345        let b = MainContext::new();
346
347        assert!(!is_same_context(&a, &b));
348
349        a.with_thread_default(|| {
350            let t = MainContext::thread_default().unwrap();
351            assert!(is_same_context(&a, &t));
352
353            b.with_thread_default(|| {
354                let t = MainContext::thread_default().unwrap();
355                assert!(is_same_context(&b, &t));
356            })
357            .unwrap();
358
359            let t = MainContext::thread_default().unwrap();
360            assert!(is_same_context(&a, &t));
361        })
362        .unwrap();
363    }
364
365    #[test]
366    fn test_with_thread_default_is_panic_safe() {
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            let result = panic::catch_unwind(|| {
377                b.with_thread_default(|| {
378                    panic!();
379                })
380                .unwrap();
381            });
382            assert!(result.is_err());
383
384            let t = MainContext::thread_default().unwrap();
385            assert!(is_same_context(&a, &t));
386        })
387        .unwrap();
388    }
389}