From 07f8a5daa31edc7ec6d1a252b7afe62fd0575309 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 25 Jul 2023 22:22:02 +0200 Subject: [PATCH] gmain: drop owner check assertion in g_main_context_release() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As commit 44616ebafdca ('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 3926af723a74 ('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 e26a8a59813c ('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] https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/9f01cff04f4a0aac5e4438e6bdb0e5c68594a706/src/libnm-glib-aux/nm-shared-utils.c#L4944 Related: 3926af723a74 ('gmain: Add precondition assertions to g_main_context_release()') Related: c67dd9d3fe91 ('gmain: Add a missing return on error path in g_main_context_release()') Closes #3054 --- glib/gmain.c | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/glib/gmain.c b/glib/gmain.c index ccadf76ce..906e947ac 100644 --- a/glib/gmain.c +++ b/glib/gmain.c @@ -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 didn’t + * acquire it. + * Breaking that would be quite disruptive, so we won’t 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) {