[glib: 1/2] gdebugcontrollerdbus: Track pending tasks with weak refs
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib: 1/2] gdebugcontrollerdbus: Track pending tasks with weak refs
- Date: Fri, 18 Feb 2022 10:10:57 +0000 (UTC)
commit 9652a3dd7954a51b5bcc74be8b2baf70dd661d19
Author: Philip Withnall <pwithnall endlessos org>
Date: Fri Feb 18 00:46:07 2022 +0000
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>
gio/gdebugcontrollerdbus.c | 79 ++++++++++++++++++++++++++++++++++++++--------
1 file changed, 66 insertions(+), 13 deletions(-)
---
diff --git a/gio/gdebugcontrollerdbus.c b/gio/gdebugcontrollerdbus.c
index e3dcdbde2..e989a6298 100644
--- a/gio/gdebugcontrollerdbus.c
+++ b/gio/gdebugcontrollerdbus.c
@@ -186,7 +186,7 @@ typedef struct
GCancellable *cancellable; /* (owned) */
GDBusConnection *connection; /* (owned) */
guint object_id;
- guint n_pending_authorize_tasks;
+ GPtrArray *pending_authorize_tasks; /* (element-type GWeakRef) (owned) (nullable) */
gboolean debug_enabled;
} GDebugControllerDBusPrivate;
@@ -272,6 +272,52 @@ dbus_get_property (GDBusConnection *connection,
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 won’t be
+ * filled by an element we haven’t 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);
+ }
+
+ /* Don’t need to keep the array around any more if it’s empty. */
+ if (priv->pending_authorize_tasks->len == 0)
+ g_clear_pointer (&priv->pending_authorize_tasks, g_ptr_array_unref);
+}
+
/* Called in a worker thread. */
static void
authorize_task_cb (GTask *task,
@@ -320,8 +366,9 @@ authorize_cb (GObject *object,
g_dbus_method_invocation_return_value (invocation, NULL);
}
- g_assert (priv->n_pending_authorize_tasks > 0);
- priv->n_pending_authorize_tasks--;
+ /* The GTask will stay alive for a bit longer as the worker thread is
+ * 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
@@ -349,8 +396,15 @@ dbus_method_call (GDBusConnection *connection,
g_task_set_source_tag (task, dbus_method_call);
g_task_set_task_data (task, g_object_ref (invocation), (GDestroyNotify) g_object_unref);
- g_assert (priv->n_pending_authorize_tasks < G_MAXUINT);
- priv->n_pending_authorize_tasks++;
+ /* Track the pending #GTask with a weak ref as its final strong ref could
+ * 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
* 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);
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->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
* unregistered and the cancellable cancelled.
*
- * FIXME: There is still a narrow race here where (we think) the worker thread
- * 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
+ * The loop will never terminate if g_debug_controller_dbus_stop() is
* called from within an ::authorize callback.
*
* See discussion in https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2486 */
- while (priv->n_pending_authorize_tasks > 0)
- g_thread_yield ();
+ while (priv->pending_authorize_tasks != NULL)
+ {
+ garbage_collect_weak_refs (self);
+ g_thread_yield ();
+ }
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]