gmain: drop owner check assertion in g_main_context_release()

As commit 44616ebafd ('gmain: More explicitly document
g_main_context_release() prereqs') correctly notes, you need to have the
context acquired before releasing it (just like a ref must match an
unref).

Commit 3926af723a ('gmain: Add precondition assertions to
g_main_context_release()') then goes one step further, and requires that
the calling thread is also the owner (the thread, that acquired the
context).

This is something which has been documented by g_main_context_release()
for years:
> Releases ownership of a context previously acquired **by this thread**

With acquire/release and g_main_context_is_owner() we track the thread
that acquired the context. That is mainly useful for asserting
correctness to not accessing the context from an unexpected thread.
Note that g_main_context_acquire() returns FALSE and does nothing when
the context is already acquired from another thread. Methods like
g_main_context_{prepare,query,dispatch}() require that the calling
thread is the owner (although, they don't assert for that, which they
maybe should).

With the assertion, it means you cannot pass an acquired context to
another thread for release. Obviously, if you pass on an acquired
context to another thread, the only next thing you can do is
g_main_context_release() (no acquire,prepare,query,dispatch). But it's
still useful to be able to release it, and to be able to keep it
acquired for a prolonged time.

libnm needs that, as it integrates a GMainContext into another
GMainContext. For that, it needs to acquire the inner context and keep
it acquired for as long as the context is integrated. Otherwise, when a
source gets attached to the inner context, the inner context is not
woken up (see g_source_attach_unlocked()). In commit e26a8a5981 ('Add
G_MAIN_CONTEXT_FLAGS_OWNERLESS_POLLING'), a flag was introduced to solve
that same problem, without keeping the inner context acquired all the
time. Note that G_MAIN_CONTEXT_FLAGS_OWNERLESS_POLLING is a flag of the
GMainContext, so it only works if the user who integrates the inner
context also controls how the context was created.  For libnm, having
the inner context acquired at all times is no problem, because it's
understood that the user gives up agency on the inner context while it's
integrated. The only thing to consider is that the outer context might
be iterated on another thread. When calling prepare,query,dispatch on
the inner context, the code will notice it, release the inner context
and re-acquire it on the new thread ([1]).  This works just fine, but it
requires that g_main_context_release() works from any thread.

So, in order to not break NetworkManager, let’s drop the ownership
assertion. However, NetworkManager is strictly breaking the API contract
here, and GLib reserves the right to re-add this assertion in future.

Still keep the assertion for the owner_count.

(Commit and commit message significantly updated by Philip Withnall; all
errors are his.)

[1] 9f01cff04f/src/libnm-glib-aux/nm-shared-utils.c (L4944)

Related: 3926af723a ('gmain: Add precondition assertions to g_main_context_release()')
Related: c67dd9d3fe ('gmain: Add a missing return on error path in g_main_context_release()')

Closes #3054
This commit is contained in:
Thomas Haller 2023-07-25 22:22:02 +02:00 committed by Philip Withnall
parent e051f4abaf
commit 07f8a5daa3

View File

@ -3596,30 +3596,27 @@ g_main_context_release (GMainContext *context)
context = g_main_context_default ();
LOCK_CONTEXT (context);
#ifndef G_DISABLE_CHECKS
if (G_UNLIKELY (context->owner != G_THREAD_SELF || context->owner_count == 0))
{
GThread *context_owner = context->owner;
guint context_owner_count = context->owner_count;
UNLOCK_CONTEXT (context);
g_critical ("g_main_context_release() called on a context (%p, owner %p, "
"owner count %u) which is not acquired by the current thread",
context, context_owner, context_owner_count);
return;
}
#endif /* !G_DISABLE_CHECKS */
g_main_context_release_unlocked (context);
UNLOCK_CONTEXT (context);
}
static void
g_main_context_release_unlocked (GMainContext *context)
{
/* NOTE: We should also have the following assert here:
* g_return_if_fail (context->owner == G_THREAD_SELF);
* However, this breaks NetworkManager, which has been (non-compliantly but
* apparently safely) releasing a #GMainContext from a thread which didnt
* acquire it.
* Breaking that would be quite disruptive, so we wont do that now. However,
* GLib reserves the right to add that assertion in future, if doing so would
* allow for optimisations or refactorings. By that point, NetworkManager will
* have to have reworked its use of #GMainContext.
*
* See: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3513
*/
g_return_if_fail (context->owner_count > 0);
context->owner_count--;
if (context->owner_count == 0)
{