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