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}