gdebugcontrollerdbus: Track pending tasks with weak refs

Rather than tracking them with a counter. This should close the race in
tracking the finalisation of the tasks by the task worker thread.
There’s no way to synchronise with that thread as it’s internal to
`g_task_run_in_thread()`.

This should hopefully stop the `debugcontroller` test being flaky.

See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2486#note_1384102

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
This commit is contained in:
Philip Withnall 2022-02-18 00:46:07 +00:00
parent d286ea0c57
commit 9652a3dd79

View File

@ -186,7 +186,7 @@ typedef struct
GCancellable *cancellable; /* (owned) */ GCancellable *cancellable; /* (owned) */
GDBusConnection *connection; /* (owned) */ GDBusConnection *connection; /* (owned) */
guint object_id; guint object_id;
guint n_pending_authorize_tasks; GPtrArray *pending_authorize_tasks; /* (element-type GWeakRef) (owned) (nullable) */
gboolean debug_enabled; gboolean debug_enabled;
} GDebugControllerDBusPrivate; } GDebugControllerDBusPrivate;
@ -272,6 +272,52 @@ dbus_get_property (GDBusConnection *connection,
return NULL; return NULL;
} }
static GWeakRef *
weak_ref_new (GObject *obj)
{
GWeakRef *weak_ref = g_new0 (GWeakRef, 1);
g_weak_ref_init (weak_ref, obj);
return g_steal_pointer (&weak_ref);
}
static void
weak_ref_free (GWeakRef *weak_ref)
{
g_weak_ref_clear (weak_ref);
g_free (weak_ref);
}
/* Called in the #GMainContext which was default when the #GDebugControllerDBus
* was initialised. */
static void
garbage_collect_weak_refs (GDebugControllerDBus *self)
{
GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self);
guint i;
if (priv->pending_authorize_tasks == NULL)
return;
/* Iterate in reverse order so that if we remove an element the hole wont be
* filled by an element we havent checked yet. */
for (i = priv->pending_authorize_tasks->len; i > 0; i--)
{
GWeakRef *weak_ref = g_ptr_array_index (priv->pending_authorize_tasks, i - 1);
GObject *obj = g_weak_ref_get (weak_ref);
if (obj == NULL)
g_ptr_array_remove_index_fast (priv->pending_authorize_tasks, i - 1);
else
g_object_unref (obj);
}
/* Dont need to keep the array around any more if its empty. */
if (priv->pending_authorize_tasks->len == 0)
g_clear_pointer (&priv->pending_authorize_tasks, g_ptr_array_unref);
}
/* Called in a worker thread. */ /* Called in a worker thread. */
static void static void
authorize_task_cb (GTask *task, authorize_task_cb (GTask *task,
@ -320,8 +366,9 @@ authorize_cb (GObject *object,
g_dbus_method_invocation_return_value (invocation, NULL); g_dbus_method_invocation_return_value (invocation, NULL);
} }
g_assert (priv->n_pending_authorize_tasks > 0); /* The GTask will stay alive for a bit longer as the worker thread is
priv->n_pending_authorize_tasks--; * potentially still in the process of dropping its reference to it. */
g_assert (priv->pending_authorize_tasks != NULL && priv->pending_authorize_tasks->len > 0);
} }
/* Called in the #GMainContext which was default when the #GDebugControllerDBus /* Called in the #GMainContext which was default when the #GDebugControllerDBus
@ -349,8 +396,15 @@ dbus_method_call (GDBusConnection *connection,
g_task_set_source_tag (task, dbus_method_call); g_task_set_source_tag (task, dbus_method_call);
g_task_set_task_data (task, g_object_ref (invocation), (GDestroyNotify) g_object_unref); g_task_set_task_data (task, g_object_ref (invocation), (GDestroyNotify) g_object_unref);
g_assert (priv->n_pending_authorize_tasks < G_MAXUINT); /* Track the pending #GTask with a weak ref as its final strong ref could
priv->n_pending_authorize_tasks++; * be dropped from this thread or an arbitrary #GTask worker thread. The
* weak refs will be evaluated in g_debug_controller_dbus_stop(). */
if (priv->pending_authorize_tasks == NULL)
priv->pending_authorize_tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) weak_ref_free);
g_ptr_array_add (priv->pending_authorize_tasks, weak_ref_new (G_OBJECT (task)));
/* Take the opportunity to clean up a bit. */
garbage_collect_weak_refs (self);
/* Check the calling peer is authorised to change the debug mode. So that /* Check the calling peer is authorised to change the debug mode. So that
* the signal handler can block on checking polkit authorisation (which * the signal handler can block on checking polkit authorisation (which
@ -466,7 +520,7 @@ g_debug_controller_dbus_dispose (GObject *object)
GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self);
g_debug_controller_dbus_stop (self); g_debug_controller_dbus_stop (self);
g_assert (priv->n_pending_authorize_tasks == 0); g_assert (priv->pending_authorize_tasks == NULL);
g_clear_object (&priv->connection); g_clear_object (&priv->connection);
g_clear_object (&priv->cancellable); g_clear_object (&priv->cancellable);
@ -642,14 +696,13 @@ g_debug_controller_dbus_stop (GDebugControllerDBus *self)
* for threads to join at this point, as the D-Bus object has been * for threads to join at this point, as the D-Bus object has been
* unregistered and the cancellable cancelled. * unregistered and the cancellable cancelled.
* *
* FIXME: There is still a narrow race here where (we think) the worker thread * The loop will never terminate if g_debug_controller_dbus_stop() is
* briefly holds a reference to the #GTask after the task thread function has
* returned.
*
* The loop will also never terminate if g_debug_controller_dbus_stop() is
* called from within an ::authorize callback. * called from within an ::authorize callback.
* *
* See discussion in https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2486 */ * See discussion in https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2486 */
while (priv->n_pending_authorize_tasks > 0) while (priv->pending_authorize_tasks != NULL)
g_thread_yield (); {
garbage_collect_weak_refs (self);
g_thread_yield ();
}
} }