tests: Wait until unwatching the gdbus-testserver name has completed

Previously, the code in `ensure_gdbus_testserver_up()` created a proxy
object and watched its `name-owner` to see when the
`com.example.TestService` name appeared.

This ended up subscribing to three signals (one of them for name
ownership, and two unused for properties of the proxy), and was racy. In
particular, the `name-owner` property could be set before all D-Bus
messages had been processed — it could have been derived from getting
the owner of the name, for example.

This left unprocessed messages hanging around in the `context`, but that
context was never iterated again, which essentially leaked the
references held by those messages. That included a reference to the
`GDBusConnection`.

The first part of the fix is to simplify the code to use
`g_bus_watch_name_on_connection()`, so there’s only one signal
subscription to worry about.

The second part of the fix is to use the `GDestroyNotify` callback for
the watch data to be notified of when all D-Bus traffic has been
processed and the signal unsubscription is complete. At this point, it’s
guaranteed that there are no idle callbacks pending in the
`GMainContext`, since the `GDestroyNotify` callback is the last one
invoked on the `GMainContext`.

Essentially, this commit uses the `GDestroyNotify` callback as a
synchronisation message between the D-Bus worker thread and the thread
calling `ensure_gdbus_testserver_up()`.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Fixes: #1515
This commit is contained in:
Philip Withnall 2020-02-21 12:07:53 +00:00
parent ac274b4005
commit af6dbece87

View File

@ -86,26 +86,52 @@ _give_up (gpointer data)
g_return_val_if_reached (TRUE);
}
typedef struct
{
GMainContext *context;
gboolean name_appeared;
gboolean unwatch_complete;
} WatchData;
static void
name_appeared_cb (GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
WatchData *data = user_data;
g_assert (name_owner != NULL);
data->name_appeared = TRUE;
g_main_context_wakeup (data->context);
}
static void
watch_free_cb (gpointer user_data)
{
WatchData *data = user_data;
data->unwatch_complete = TRUE;
g_main_context_wakeup (data->context);
}
void
ensure_gdbus_testserver_up (GDBusConnection *connection,
GMainContext *context)
{
gchar *name_owner;
GSource *timeout_source = NULL;
GDBusProxy *proxy;
GError *error = NULL;
guint watch_id;
WatchData data = { context, FALSE, FALSE };
g_main_context_push_thread_default (context);
proxy = g_dbus_proxy_new_sync (connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL, /* GDBusInterfaceInfo */
"com.example.TestService", /* name */
"/com/example/TestObject", /* object path */
"com.example.Frob", /* interface */
NULL, /* GCancellable */
&error);
g_assert_no_error (error);
watch_id = g_bus_watch_name_on_connection (connection,
"com.example.TestService",
G_BUS_NAME_WATCHER_FLAGS_NONE,
name_appeared_cb,
NULL,
&data,
watch_free_cb);
timeout_source = g_timeout_source_new_seconds (60);
g_source_set_callback (timeout_source, _give_up,
@ -113,20 +139,17 @@ ensure_gdbus_testserver_up (GDBusConnection *connection,
NULL);
g_source_attach (timeout_source, context);
while (TRUE)
{
name_owner = g_dbus_proxy_get_name_owner (proxy);
while (!data.name_appeared)
g_main_context_iteration (context, TRUE);
if (name_owner != NULL)
break;
g_bus_unwatch_name (watch_id);
watch_id = 0;
g_main_context_iteration (context, TRUE);
}
while (!data.unwatch_complete)
g_main_context_iteration (context, TRUE);
g_source_destroy (timeout_source);
g_source_unref (timeout_source);
g_free (name_owner);
g_object_unref (proxy);
g_main_context_pop_thread_default (context);
}