Sync methods don't block, and async methods don't invoke callbacks in the thread-default GMainContext
Submitted by Debarshi Ray
Assigned to gri..@..e.bugs
Forked from "Synchronous methods get stuck when used in threaded paths": https://bugzilla.gnome.org/show_bug.cgi?id=762496#c1
The use-case for the original complaint was:
A GMainLoop-based program that uses asynchronous operations to avoid blocking the main thread. Some of these asynchronous operations are offered by base classes or interfaces, where the implementation comes from a child or implementation class. It is a common pattern to let the base class / interface deal with all the async boilerplate, and have the child / implementation only provide a "simple" synchronous method which is run in a thread.
See the attached test case.
However, this is a sympton of a more generic problem. Let's take grl_source_resolve as an example.
The asynchronous variant uses g_idle_add* to dispatch the callbacks. ie. the callback is run in the NULL or global GMainContext. Instead, the GIO idiom is to use the thread-default GMainContext at the time of invoking the async method. Both GTask and GSimpleAsyncResult use g_main_context_ref_thread_default to track the thread-default context at the time of their construction, and use it to invoke callbacks.
This is important because sometimes the synchronous variant of a method is implemented in terms of its asynchronous counterpart by wrapping it in a new thread-default GMainContext. (This sync method might then be used by an application in a worker thread.) All the child async methods in the chain should invoke their callbacks in the thread-default GMainContext. Otherwise, even though the chain was initiated in a thread, they would be invoking their callbacks, and the remainder of the chain, in the global GMainContext, which usually means the main thread.
The synchronous variant, grl_source_resolve_sync, is almost like the one in the previous paragraph. Except it doesn't push a fresh new GMainContext before calling its asynchronous counterpart. This means that it is iterating a GMainContext with GSources that are not related to this call, and potentially triggering them. Hence, the sync call isn't really blocking because random unrelated callbacks maybe be invoked while it is running.