2010-05-06 20:13:59 +02:00
|
|
|
/* GLib testing framework examples and tests
|
|
|
|
*
|
2010-05-10 14:07:28 +02:00
|
|
|
* Copyright (C) 2008-2010 Red Hat, Inc.
|
2010-05-06 20:13:59 +02:00
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
2017-05-27 17:19:21 +02:00
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2010-05-06 20:13:59 +02:00
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General
|
2014-01-23 12:58:29 +01:00
|
|
|
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
2010-05-06 20:13:59 +02:00
|
|
|
*
|
|
|
|
* Author: David Zeuthen <davidz@redhat.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <gio/gio.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "gdbus-tests.h"
|
|
|
|
|
|
|
|
/* all tests rely on a shared mainloop */
|
|
|
|
static GMainLoop *loop;
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
/* Test that g_bus_own_name() works correctly */
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
gboolean expect_null_connection;
|
|
|
|
guint num_bus_acquired;
|
|
|
|
guint num_acquired;
|
|
|
|
guint num_lost;
|
|
|
|
guint num_free_func;
|
|
|
|
} OwnNameData;
|
|
|
|
|
|
|
|
static void
|
|
|
|
own_name_data_free_func (OwnNameData *data)
|
|
|
|
{
|
|
|
|
data->num_free_func++;
|
|
|
|
g_main_loop_quit (loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
bus_acquired_handler (GDBusConnection *connection,
|
|
|
|
const gchar *name,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
OwnNameData *data = user_data;
|
|
|
|
g_dbus_connection_set_exit_on_close (connection, FALSE);
|
|
|
|
data->num_bus_acquired += 1;
|
|
|
|
g_main_loop_quit (loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
name_acquired_handler (GDBusConnection *connection,
|
|
|
|
const gchar *name,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
OwnNameData *data = user_data;
|
|
|
|
data->num_acquired += 1;
|
|
|
|
g_main_loop_quit (loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
name_lost_handler (GDBusConnection *connection,
|
|
|
|
const gchar *name,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
OwnNameData *data = user_data;
|
|
|
|
if (data->expect_null_connection)
|
|
|
|
{
|
|
|
|
g_assert (connection == NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_assert (connection != NULL);
|
|
|
|
g_dbus_connection_set_exit_on_close (connection, FALSE);
|
|
|
|
}
|
|
|
|
data->num_lost += 1;
|
|
|
|
g_main_loop_quit (loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_bus_own_name (void)
|
|
|
|
{
|
|
|
|
guint id;
|
|
|
|
guint id2;
|
|
|
|
OwnNameData data;
|
|
|
|
OwnNameData data2;
|
|
|
|
const gchar *name;
|
|
|
|
GDBusConnection *c;
|
|
|
|
GError *error;
|
|
|
|
gboolean name_has_owner_reply;
|
|
|
|
GDBusConnection *c2;
|
|
|
|
GVariant *result;
|
|
|
|
|
|
|
|
error = NULL;
|
|
|
|
name = "org.gtk.GDBus.Name1";
|
|
|
|
|
|
|
|
/*
|
|
|
|
* First check that name_lost_handler() is invoked if there is no bus.
|
|
|
|
*
|
|
|
|
* Also make sure name_lost_handler() isn't invoked when unowning the name.
|
|
|
|
*/
|
|
|
|
data.num_bus_acquired = 0;
|
|
|
|
data.num_free_func = 0;
|
|
|
|
data.num_acquired = 0;
|
|
|
|
data.num_lost = 0;
|
|
|
|
data.expect_null_connection = TRUE;
|
|
|
|
id = g_bus_own_name (G_BUS_TYPE_SESSION,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
|
|
bus_acquired_handler,
|
|
|
|
name_acquired_handler,
|
|
|
|
name_lost_handler,
|
|
|
|
&data,
|
|
|
|
(GDestroyNotify) own_name_data_free_func);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 1);
|
|
|
|
g_bus_unown_name (id);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_free_func, ==, 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Bring up a bus, then own a name and check bus_acquired_handler() then name_acquired_handler() is invoked.
|
|
|
|
*/
|
|
|
|
session_bus_up ();
|
|
|
|
data.num_bus_acquired = 0;
|
|
|
|
data.num_acquired = 0;
|
|
|
|
data.num_lost = 0;
|
|
|
|
data.expect_null_connection = FALSE;
|
|
|
|
id = g_bus_own_name (G_BUS_TYPE_SESSION,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
|
|
bus_acquired_handler,
|
|
|
|
name_acquired_handler,
|
|
|
|
name_lost_handler,
|
|
|
|
&data,
|
|
|
|
(GDestroyNotify) own_name_data_free_func);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check that the name was actually acquired.
|
|
|
|
*/
|
|
|
|
c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
|
|
g_assert (c != NULL);
|
|
|
|
g_assert (!g_dbus_connection_is_closed (c));
|
2010-05-10 17:47:08 +02:00
|
|
|
result = g_dbus_connection_call_sync (c,
|
|
|
|
"org.freedesktop.DBus", /* bus name */
|
|
|
|
"/org/freedesktop/DBus", /* object path */
|
|
|
|
"org.freedesktop.DBus", /* interface name */
|
|
|
|
"NameHasOwner", /* method name */
|
|
|
|
g_variant_new ("(s)", name),
|
2010-05-24 22:46:24 +02:00
|
|
|
G_VARIANT_TYPE ("(b)"),
|
2010-05-10 17:47:08 +02:00
|
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
|
|
-1,
|
|
|
|
NULL,
|
|
|
|
&error);
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_no_error (error);
|
|
|
|
g_assert (result != NULL);
|
|
|
|
g_variant_get (result, "(b)", &name_has_owner_reply);
|
|
|
|
g_assert (name_has_owner_reply);
|
|
|
|
g_variant_unref (result);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Stop owning the name - this should invoke our free func
|
|
|
|
*/
|
|
|
|
g_bus_unown_name (id);
|
gdbusnameowning: Fix race between connection shutdown and NameLost
As with all D-Bus signal subscriptions, it’s possible for a signal
callback to be invoked in one thread (T1) while another thread (T2) is
unsubscribing from that signal. In this case, T1 is the main thread, and
T2 is the D-Bus connection worker thread which is unsubscribing all
signals as it’s in the process of closing.
Due to this possibility, all `user_data` for signal callbacks needs to
be referenced outside the lifecycle of the code which
subscribes/unsubscribes the signal. In other words, it’s not safe to
subscribe to a signal, store the subscription ID in a struct,
unsubscribe from the signal when freeing the struct, and dereference the
struct in the signal callback. The data passed to the signal callback
has to have its own strong reference.
Instead, it’s safe to subscribe to a signal and add a strong reference
to the struct, store the subscription ID in that struct, and unsubscribe
from the signal when the last external reference to your struct is
dropped. That unsubscription should break the refcount cycle between the
signal connection and the struct, and allow the struct to be completely
freed. Only with that approach is it safe to dereference the struct in
the signal callback, if there’s any possibility that the signal might be
unsubscribed from a separate thread.
The tests need specific additional main loop cycles to completely emit
the NameLost signal callback. Ideally they need refactoring, but this
will do (1000 test cycles passed).
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Fixes: #978
2020-01-17 21:00:22 +01:00
|
|
|
g_main_loop_run (loop);
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data.num_free_func, ==, 2);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check that the name was actually released.
|
|
|
|
*/
|
2010-05-10 17:47:08 +02:00
|
|
|
result = g_dbus_connection_call_sync (c,
|
|
|
|
"org.freedesktop.DBus", /* bus name */
|
|
|
|
"/org/freedesktop/DBus", /* object path */
|
|
|
|
"org.freedesktop.DBus", /* interface name */
|
|
|
|
"NameHasOwner", /* method name */
|
|
|
|
g_variant_new ("(s)", name),
|
2010-05-24 22:46:24 +02:00
|
|
|
G_VARIANT_TYPE ("(b)"),
|
2010-05-10 17:47:08 +02:00
|
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
|
|
-1,
|
|
|
|
NULL,
|
|
|
|
&error);
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_no_error (error);
|
|
|
|
g_assert (result != NULL);
|
|
|
|
g_variant_get (result, "(b)", &name_has_owner_reply);
|
|
|
|
g_assert (!name_has_owner_reply);
|
|
|
|
g_variant_unref (result);
|
|
|
|
|
2011-10-27 16:30:58 +02:00
|
|
|
/* Now try owning the name and then immediately decide to unown the name */
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_free_func, ==, 2);
|
|
|
|
id = g_bus_own_name (G_BUS_TYPE_SESSION,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
|
|
bus_acquired_handler,
|
|
|
|
name_acquired_handler,
|
|
|
|
name_lost_handler,
|
|
|
|
&data,
|
|
|
|
(GDestroyNotify) own_name_data_free_func);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_free_func, ==, 2);
|
|
|
|
g_bus_unown_name (id);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_free_func, ==, 2);
|
|
|
|
g_main_loop_run (loop); /* the GDestroyNotify is called in idle because the bus is acquired in idle */
|
|
|
|
g_assert_cmpint (data.num_free_func, ==, 3);
|
|
|
|
|
2010-05-06 20:13:59 +02:00
|
|
|
/*
|
|
|
|
* Own the name again.
|
|
|
|
*/
|
|
|
|
data.num_bus_acquired = 0;
|
|
|
|
data.num_acquired = 0;
|
|
|
|
data.num_lost = 0;
|
|
|
|
data.expect_null_connection = FALSE;
|
2010-08-07 22:27:38 +02:00
|
|
|
id = g_bus_own_name_with_closures (G_BUS_TYPE_SESSION,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
|
|
g_cclosure_new (G_CALLBACK (bus_acquired_handler),
|
|
|
|
&data,
|
|
|
|
NULL),
|
|
|
|
g_cclosure_new (G_CALLBACK (name_acquired_handler),
|
|
|
|
&data,
|
|
|
|
NULL),
|
|
|
|
g_cclosure_new (G_CALLBACK (name_lost_handler),
|
|
|
|
&data,
|
|
|
|
(GClosureNotify) own_name_data_free_func));
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Try owning the name with another object on the same connection - this should
|
|
|
|
* fail because we already own the name.
|
|
|
|
*/
|
|
|
|
data2.num_free_func = 0;
|
|
|
|
data2.num_bus_acquired = 0;
|
|
|
|
data2.num_acquired = 0;
|
|
|
|
data2.num_lost = 0;
|
|
|
|
data2.expect_null_connection = FALSE;
|
|
|
|
id2 = g_bus_own_name (G_BUS_TYPE_SESSION,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
|
|
bus_acquired_handler,
|
|
|
|
name_acquired_handler,
|
|
|
|
name_lost_handler,
|
|
|
|
&data2,
|
|
|
|
(GDestroyNotify) own_name_data_free_func);
|
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 1);
|
|
|
|
g_bus_unown_name (id2);
|
2020-02-16 10:57:30 +01:00
|
|
|
g_main_loop_run (loop);
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 1);
|
|
|
|
g_assert_cmpint (data2.num_free_func, ==, 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create a secondary (e.g. private) connection and try owning the name on that
|
|
|
|
* connection. This should fail both with and without _REPLACE because we
|
|
|
|
* didn't specify ALLOW_REPLACEMENT.
|
|
|
|
*/
|
|
|
|
c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
|
|
g_assert (c2 != NULL);
|
|
|
|
g_assert (!g_dbus_connection_is_closed (c2));
|
|
|
|
/* first without _REPLACE */
|
|
|
|
data2.num_bus_acquired = 0;
|
|
|
|
data2.num_acquired = 0;
|
|
|
|
data2.num_lost = 0;
|
|
|
|
data2.expect_null_connection = FALSE;
|
|
|
|
data2.num_free_func = 0;
|
|
|
|
id2 = g_bus_own_name_on_connection (c2,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
|
|
name_acquired_handler,
|
|
|
|
name_lost_handler,
|
|
|
|
&data2,
|
|
|
|
(GDestroyNotify) own_name_data_free_func);
|
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 1);
|
|
|
|
g_bus_unown_name (id2);
|
gdbusnameowning: Fix race between connection shutdown and NameLost
As with all D-Bus signal subscriptions, it’s possible for a signal
callback to be invoked in one thread (T1) while another thread (T2) is
unsubscribing from that signal. In this case, T1 is the main thread, and
T2 is the D-Bus connection worker thread which is unsubscribing all
signals as it’s in the process of closing.
Due to this possibility, all `user_data` for signal callbacks needs to
be referenced outside the lifecycle of the code which
subscribes/unsubscribes the signal. In other words, it’s not safe to
subscribe to a signal, store the subscription ID in a struct,
unsubscribe from the signal when freeing the struct, and dereference the
struct in the signal callback. The data passed to the signal callback
has to have its own strong reference.
Instead, it’s safe to subscribe to a signal and add a strong reference
to the struct, store the subscription ID in that struct, and unsubscribe
from the signal when the last external reference to your struct is
dropped. That unsubscription should break the refcount cycle between the
signal connection and the struct, and allow the struct to be completely
freed. Only with that approach is it safe to dereference the struct in
the signal callback, if there’s any possibility that the signal might be
unsubscribed from a separate thread.
The tests need specific additional main loop cycles to completely emit
the NameLost signal callback. Ideally they need refactoring, but this
will do (1000 test cycles passed).
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Fixes: #978
2020-01-17 21:00:22 +01:00
|
|
|
g_main_loop_run (loop);
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 1);
|
|
|
|
g_assert_cmpint (data2.num_free_func, ==, 1);
|
|
|
|
/* then with _REPLACE */
|
|
|
|
data2.num_bus_acquired = 0;
|
|
|
|
data2.num_acquired = 0;
|
|
|
|
data2.num_lost = 0;
|
|
|
|
data2.expect_null_connection = FALSE;
|
|
|
|
data2.num_free_func = 0;
|
|
|
|
id2 = g_bus_own_name_on_connection (c2,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_REPLACE,
|
|
|
|
name_acquired_handler,
|
|
|
|
name_lost_handler,
|
|
|
|
&data2,
|
|
|
|
(GDestroyNotify) own_name_data_free_func);
|
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 1);
|
|
|
|
g_bus_unown_name (id2);
|
gdbusnameowning: Fix race between connection shutdown and NameLost
As with all D-Bus signal subscriptions, it’s possible for a signal
callback to be invoked in one thread (T1) while another thread (T2) is
unsubscribing from that signal. In this case, T1 is the main thread, and
T2 is the D-Bus connection worker thread which is unsubscribing all
signals as it’s in the process of closing.
Due to this possibility, all `user_data` for signal callbacks needs to
be referenced outside the lifecycle of the code which
subscribes/unsubscribes the signal. In other words, it’s not safe to
subscribe to a signal, store the subscription ID in a struct,
unsubscribe from the signal when freeing the struct, and dereference the
struct in the signal callback. The data passed to the signal callback
has to have its own strong reference.
Instead, it’s safe to subscribe to a signal and add a strong reference
to the struct, store the subscription ID in that struct, and unsubscribe
from the signal when the last external reference to your struct is
dropped. That unsubscription should break the refcount cycle between the
signal connection and the struct, and allow the struct to be completely
freed. Only with that approach is it safe to dereference the struct in
the signal callback, if there’s any possibility that the signal might be
unsubscribed from a separate thread.
The tests need specific additional main loop cycles to completely emit
the NameLost signal callback. Ideally they need refactoring, but this
will do (1000 test cycles passed).
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Fixes: #978
2020-01-17 21:00:22 +01:00
|
|
|
g_main_loop_run (loop);
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 1);
|
|
|
|
g_assert_cmpint (data2.num_free_func, ==, 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Stop owning the name and grab it again with _ALLOW_REPLACEMENT.
|
|
|
|
*/
|
|
|
|
data.expect_null_connection = FALSE;
|
|
|
|
g_bus_unown_name (id);
|
gdbusnameowning: Fix race between connection shutdown and NameLost
As with all D-Bus signal subscriptions, it’s possible for a signal
callback to be invoked in one thread (T1) while another thread (T2) is
unsubscribing from that signal. In this case, T1 is the main thread, and
T2 is the D-Bus connection worker thread which is unsubscribing all
signals as it’s in the process of closing.
Due to this possibility, all `user_data` for signal callbacks needs to
be referenced outside the lifecycle of the code which
subscribes/unsubscribes the signal. In other words, it’s not safe to
subscribe to a signal, store the subscription ID in a struct,
unsubscribe from the signal when freeing the struct, and dereference the
struct in the signal callback. The data passed to the signal callback
has to have its own strong reference.
Instead, it’s safe to subscribe to a signal and add a strong reference
to the struct, store the subscription ID in that struct, and unsubscribe
from the signal when the last external reference to your struct is
dropped. That unsubscription should break the refcount cycle between the
signal connection and the struct, and allow the struct to be completely
freed. Only with that approach is it safe to dereference the struct in
the signal callback, if there’s any possibility that the signal might be
unsubscribed from a separate thread.
The tests need specific additional main loop cycles to completely emit
the NameLost signal callback. Ideally they need refactoring, but this
will do (1000 test cycles passed).
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Fixes: #978
2020-01-17 21:00:22 +01:00
|
|
|
g_main_loop_run (loop);
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
2011-10-27 16:30:58 +02:00
|
|
|
g_assert_cmpint (data.num_free_func, ==, 4);
|
2010-05-06 20:13:59 +02:00
|
|
|
/* grab it again */
|
|
|
|
data.num_bus_acquired = 0;
|
|
|
|
data.num_acquired = 0;
|
|
|
|
data.num_lost = 0;
|
|
|
|
data.expect_null_connection = FALSE;
|
|
|
|
id = g_bus_own_name (G_BUS_TYPE_SESSION,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
|
|
|
|
bus_acquired_handler,
|
|
|
|
name_acquired_handler,
|
|
|
|
name_lost_handler,
|
|
|
|
&data,
|
|
|
|
(GDestroyNotify) own_name_data_free_func);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_bus_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now try to grab the name from the secondary connection.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
/* first without _REPLACE - this won't make us acquire the name */
|
|
|
|
data2.num_bus_acquired = 0;
|
|
|
|
data2.num_acquired = 0;
|
|
|
|
data2.num_lost = 0;
|
|
|
|
data2.expect_null_connection = FALSE;
|
|
|
|
data2.num_free_func = 0;
|
|
|
|
id2 = g_bus_own_name_on_connection (c2,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
|
|
name_acquired_handler,
|
|
|
|
name_lost_handler,
|
|
|
|
&data2,
|
|
|
|
(GDestroyNotify) own_name_data_free_func);
|
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 1);
|
|
|
|
g_bus_unown_name (id2);
|
gdbusnameowning: Fix race between connection shutdown and NameLost
As with all D-Bus signal subscriptions, it’s possible for a signal
callback to be invoked in one thread (T1) while another thread (T2) is
unsubscribing from that signal. In this case, T1 is the main thread, and
T2 is the D-Bus connection worker thread which is unsubscribing all
signals as it’s in the process of closing.
Due to this possibility, all `user_data` for signal callbacks needs to
be referenced outside the lifecycle of the code which
subscribes/unsubscribes the signal. In other words, it’s not safe to
subscribe to a signal, store the subscription ID in a struct,
unsubscribe from the signal when freeing the struct, and dereference the
struct in the signal callback. The data passed to the signal callback
has to have its own strong reference.
Instead, it’s safe to subscribe to a signal and add a strong reference
to the struct, store the subscription ID in that struct, and unsubscribe
from the signal when the last external reference to your struct is
dropped. That unsubscription should break the refcount cycle between the
signal connection and the struct, and allow the struct to be completely
freed. Only with that approach is it safe to dereference the struct in
the signal callback, if there’s any possibility that the signal might be
unsubscribed from a separate thread.
The tests need specific additional main loop cycles to completely emit
the NameLost signal callback. Ideally they need refactoring, but this
will do (1000 test cycles passed).
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Fixes: #978
2020-01-17 21:00:22 +01:00
|
|
|
g_main_loop_run (loop);
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 1);
|
|
|
|
g_assert_cmpint (data2.num_free_func, ==, 1);
|
|
|
|
/* then with _REPLACE - here we should acquire the name - e.g. owner should lose it
|
|
|
|
* and owner2 should acquire it */
|
|
|
|
data2.num_bus_acquired = 0;
|
|
|
|
data2.num_acquired = 0;
|
|
|
|
data2.num_lost = 0;
|
|
|
|
data2.expect_null_connection = FALSE;
|
|
|
|
data2.num_free_func = 0;
|
|
|
|
id2 = g_bus_own_name_on_connection (c2,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_REPLACE,
|
|
|
|
name_acquired_handler,
|
|
|
|
name_lost_handler,
|
|
|
|
&data2,
|
|
|
|
(GDestroyNotify) own_name_data_free_func);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 0);
|
|
|
|
/* wait for handlers for both owner and owner2 to fire */
|
|
|
|
while (data.num_lost == 0 || data2.num_acquired == 0)
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 1);
|
|
|
|
g_assert_cmpint (data2.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data2.num_lost, ==, 0);
|
|
|
|
g_assert_cmpint (data2.num_bus_acquired, ==, 0);
|
|
|
|
/* ok, make owner2 release the name - then wait for owner to automagically reacquire it */
|
|
|
|
g_bus_unown_name (id2);
|
|
|
|
g_main_loop_run (loop);
|
gdbusnameowning: Fix race between connection shutdown and NameLost
As with all D-Bus signal subscriptions, it’s possible for a signal
callback to be invoked in one thread (T1) while another thread (T2) is
unsubscribing from that signal. In this case, T1 is the main thread, and
T2 is the D-Bus connection worker thread which is unsubscribing all
signals as it’s in the process of closing.
Due to this possibility, all `user_data` for signal callbacks needs to
be referenced outside the lifecycle of the code which
subscribes/unsubscribes the signal. In other words, it’s not safe to
subscribe to a signal, store the subscription ID in a struct,
unsubscribe from the signal when freeing the struct, and dereference the
struct in the signal callback. The data passed to the signal callback
has to have its own strong reference.
Instead, it’s safe to subscribe to a signal and add a strong reference
to the struct, store the subscription ID in that struct, and unsubscribe
from the signal when the last external reference to your struct is
dropped. That unsubscription should break the refcount cycle between the
signal connection and the struct, and allow the struct to be completely
freed. Only with that approach is it safe to dereference the struct in
the signal callback, if there’s any possibility that the signal might be
unsubscribed from a separate thread.
The tests need specific additional main loop cycles to completely emit
the NameLost signal callback. Ideally they need refactoring, but this
will do (1000 test cycles passed).
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Fixes: #978
2020-01-17 21:00:22 +01:00
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data2.num_free_func, ==, 1);
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data.num_acquired, ==, 2);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Finally, nuke the bus and check name_lost_handler() is invoked.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
data.expect_null_connection = TRUE;
|
2012-04-18 23:28:17 +02:00
|
|
|
session_bus_stop ();
|
2010-05-06 20:13:59 +02:00
|
|
|
while (data.num_lost != 2)
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 2);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 2);
|
|
|
|
g_bus_unown_name (id);
|
gdbusnameowning: Fix race between connection shutdown and NameLost
As with all D-Bus signal subscriptions, it’s possible for a signal
callback to be invoked in one thread (T1) while another thread (T2) is
unsubscribing from that signal. In this case, T1 is the main thread, and
T2 is the D-Bus connection worker thread which is unsubscribing all
signals as it’s in the process of closing.
Due to this possibility, all `user_data` for signal callbacks needs to
be referenced outside the lifecycle of the code which
subscribes/unsubscribes the signal. In other words, it’s not safe to
subscribe to a signal, store the subscription ID in a struct,
unsubscribe from the signal when freeing the struct, and dereference the
struct in the signal callback. The data passed to the signal callback
has to have its own strong reference.
Instead, it’s safe to subscribe to a signal and add a strong reference
to the struct, store the subscription ID in that struct, and unsubscribe
from the signal when the last external reference to your struct is
dropped. That unsubscription should break the refcount cycle between the
signal connection and the struct, and allow the struct to be completely
freed. Only with that approach is it safe to dereference the struct in
the signal callback, if there’s any possibility that the signal might be
unsubscribed from a separate thread.
The tests need specific additional main loop cycles to completely emit
the NameLost signal callback. Ideally they need refactoring, but this
will do (1000 test cycles passed).
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Fixes: #978
2020-01-17 21:00:22 +01:00
|
|
|
g_main_loop_run (loop);
|
2011-10-27 16:30:58 +02:00
|
|
|
g_assert_cmpint (data.num_free_func, ==, 5);
|
2010-05-06 20:13:59 +02:00
|
|
|
|
|
|
|
g_object_unref (c);
|
|
|
|
g_object_unref (c2);
|
2012-04-18 23:28:17 +02:00
|
|
|
|
|
|
|
session_bus_down ();
|
2010-05-06 20:13:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
/* Test that g_bus_watch_name() works correctly */
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
gboolean expect_null_connection;
|
|
|
|
guint num_acquired;
|
|
|
|
guint num_lost;
|
|
|
|
guint num_appeared;
|
|
|
|
guint num_vanished;
|
|
|
|
guint num_free_func;
|
|
|
|
} WatchNameData;
|
|
|
|
|
|
|
|
static void
|
|
|
|
watch_name_data_free_func (WatchNameData *data)
|
|
|
|
{
|
|
|
|
data->num_free_func++;
|
|
|
|
g_main_loop_quit (loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
w_bus_acquired_handler (GDBusConnection *connection,
|
|
|
|
const gchar *name,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
w_name_acquired_handler (GDBusConnection *connection,
|
|
|
|
const gchar *name,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
WatchNameData *data = user_data;
|
|
|
|
data->num_acquired += 1;
|
|
|
|
g_main_loop_quit (loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
w_name_lost_handler (GDBusConnection *connection,
|
|
|
|
const gchar *name,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
WatchNameData *data = user_data;
|
|
|
|
data->num_lost += 1;
|
|
|
|
g_main_loop_quit (loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
name_appeared_handler (GDBusConnection *connection,
|
|
|
|
const gchar *name,
|
|
|
|
const gchar *name_owner,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
WatchNameData *data = user_data;
|
2021-01-16 08:03:34 +01:00
|
|
|
|
2010-05-06 20:13:59 +02:00
|
|
|
if (data->expect_null_connection)
|
|
|
|
{
|
|
|
|
g_assert (connection == NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_assert (connection != NULL);
|
|
|
|
g_dbus_connection_set_exit_on_close (connection, FALSE);
|
|
|
|
}
|
|
|
|
data->num_appeared += 1;
|
|
|
|
g_main_loop_quit (loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
name_vanished_handler (GDBusConnection *connection,
|
|
|
|
const gchar *name,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
WatchNameData *data = user_data;
|
2021-01-16 08:03:34 +01:00
|
|
|
|
2010-05-06 20:13:59 +02:00
|
|
|
if (data->expect_null_connection)
|
|
|
|
{
|
|
|
|
g_assert (connection == NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_assert (connection != NULL);
|
|
|
|
g_dbus_connection_set_exit_on_close (connection, FALSE);
|
|
|
|
}
|
|
|
|
data->num_vanished += 1;
|
|
|
|
g_main_loop_quit (loop);
|
|
|
|
}
|
|
|
|
|
2021-01-16 08:03:34 +01:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
guint watcher_flags;
|
|
|
|
gboolean watch_with_closures;
|
|
|
|
gboolean existing_service;
|
|
|
|
} WatchNameTest;
|
|
|
|
|
|
|
|
static const WatchNameTest watch_no_closures_no_flags = {
|
|
|
|
.watcher_flags = G_BUS_NAME_WATCHER_FLAGS_NONE,
|
|
|
|
.watch_with_closures = FALSE,
|
|
|
|
.existing_service = FALSE
|
|
|
|
};
|
|
|
|
|
|
|
|
static const WatchNameTest watch_no_closures_flags_auto_start = {
|
|
|
|
.watcher_flags = G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
|
|
|
.watch_with_closures = FALSE,
|
|
|
|
.existing_service = FALSE
|
|
|
|
};
|
|
|
|
|
|
|
|
static const WatchNameTest watch_no_closures_flags_auto_start_service_exist = {
|
|
|
|
.watcher_flags = G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
|
|
|
.watch_with_closures = FALSE,
|
|
|
|
.existing_service = TRUE
|
|
|
|
};
|
|
|
|
|
|
|
|
static const WatchNameTest watch_closures_no_flags = {
|
|
|
|
.watcher_flags = G_BUS_NAME_WATCHER_FLAGS_NONE,
|
|
|
|
.watch_with_closures = TRUE,
|
|
|
|
.existing_service = FALSE
|
|
|
|
};
|
|
|
|
|
|
|
|
static const WatchNameTest watch_closures_flags_auto_start = {
|
|
|
|
.watcher_flags = G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
|
|
|
.watch_with_closures = TRUE,
|
|
|
|
.existing_service = FALSE
|
|
|
|
};
|
|
|
|
|
2010-05-06 20:13:59 +02:00
|
|
|
static void
|
2021-01-16 08:03:34 +01:00
|
|
|
stop_service (GDBusConnection *connection,
|
|
|
|
WatchNameData *data)
|
|
|
|
{
|
|
|
|
GError *error = NULL;
|
|
|
|
GDBusProxy *proxy = NULL;
|
2021-01-26 15:27:01 +01:00
|
|
|
GVariant *result = NULL;
|
2021-01-16 08:03:34 +01:00
|
|
|
|
|
|
|
data->num_vanished = 0;
|
|
|
|
|
|
|
|
proxy = g_dbus_proxy_new_sync (connection,
|
|
|
|
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
|
|
|
|
NULL,
|
|
|
|
"org.gtk.GDBus.FakeService",
|
|
|
|
"/org/gtk/GDBus/FakeService",
|
|
|
|
"org.gtk.GDBus.FakeService",
|
|
|
|
NULL,
|
|
|
|
&error);
|
|
|
|
g_assert_no_error (error);
|
|
|
|
|
2021-01-26 15:27:01 +01:00
|
|
|
result = g_dbus_proxy_call_sync (proxy,
|
|
|
|
"Quit",
|
|
|
|
NULL,
|
|
|
|
G_DBUS_CALL_FLAGS_NO_AUTO_START,
|
|
|
|
100,
|
|
|
|
NULL,
|
|
|
|
&error);
|
2021-01-16 08:03:34 +01:00
|
|
|
g_assert_no_error (error);
|
|
|
|
g_object_unref (proxy);
|
2021-01-26 15:27:01 +01:00
|
|
|
if (result)
|
|
|
|
g_variant_unref (result);
|
2021-01-16 08:03:34 +01:00
|
|
|
while (data->num_vanished == 0)
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_bus_watch_name (gconstpointer d)
|
2010-05-06 20:13:59 +02:00
|
|
|
{
|
|
|
|
WatchNameData data;
|
|
|
|
guint id;
|
|
|
|
guint owner_id;
|
2010-08-07 22:27:38 +02:00
|
|
|
GDBusConnection *connection;
|
2021-01-16 08:03:34 +01:00
|
|
|
const WatchNameTest *watch_name_test;
|
|
|
|
const gchar *name;
|
|
|
|
|
|
|
|
watch_name_test = (WatchNameTest *) d;
|
|
|
|
|
|
|
|
if (watch_name_test->existing_service)
|
|
|
|
{
|
|
|
|
name = "org.gtk.GDBus.FakeService";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
name = "org.gtk.GDBus.Name1";
|
|
|
|
}
|
2010-05-06 20:13:59 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* First check that name_vanished_handler() is invoked if there is no bus.
|
|
|
|
*
|
|
|
|
* Also make sure name_vanished_handler() isn't invoked when unwatching the name.
|
|
|
|
*/
|
|
|
|
data.num_free_func = 0;
|
|
|
|
data.num_appeared = 0;
|
|
|
|
data.num_vanished = 0;
|
|
|
|
data.expect_null_connection = TRUE;
|
|
|
|
id = g_bus_watch_name (G_BUS_TYPE_SESSION,
|
2021-01-16 08:03:34 +01:00
|
|
|
name,
|
|
|
|
watch_name_test->watcher_flags,
|
2010-05-06 20:13:59 +02:00
|
|
|
name_appeared_handler,
|
|
|
|
name_vanished_handler,
|
|
|
|
&data,
|
|
|
|
(GDestroyNotify) watch_name_data_free_func);
|
|
|
|
g_assert_cmpint (data.num_appeared, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_appeared, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 1);
|
|
|
|
g_bus_unwatch_name (id);
|
|
|
|
g_assert_cmpint (data.num_appeared, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_free_func, ==, 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now bring up a bus, own a name, and then start watching it.
|
|
|
|
*/
|
|
|
|
session_bus_up ();
|
|
|
|
/* own the name */
|
|
|
|
data.num_free_func = 0;
|
|
|
|
data.num_acquired = 0;
|
|
|
|
data.num_lost = 0;
|
|
|
|
data.expect_null_connection = FALSE;
|
|
|
|
owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
|
2021-01-16 08:03:34 +01:00
|
|
|
name,
|
2010-05-06 20:13:59 +02:00
|
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
|
|
w_bus_acquired_handler,
|
|
|
|
w_name_acquired_handler,
|
|
|
|
w_name_lost_handler,
|
|
|
|
&data,
|
|
|
|
(GDestroyNotify) watch_name_data_free_func);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
2021-01-16 08:03:34 +01:00
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
2010-08-07 22:27:38 +02:00
|
|
|
|
|
|
|
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
|
|
g_assert (connection != NULL);
|
|
|
|
|
2010-05-06 20:13:59 +02:00
|
|
|
/* now watch the name */
|
|
|
|
data.num_appeared = 0;
|
|
|
|
data.num_vanished = 0;
|
2021-01-16 08:03:34 +01:00
|
|
|
if (watch_name_test->watch_with_closures)
|
|
|
|
{
|
|
|
|
id = g_bus_watch_name_on_connection_with_closures (connection,
|
|
|
|
name,
|
|
|
|
watch_name_test->watcher_flags,
|
|
|
|
g_cclosure_new (G_CALLBACK (name_appeared_handler),
|
|
|
|
&data,
|
|
|
|
NULL),
|
|
|
|
g_cclosure_new (G_CALLBACK (name_vanished_handler),
|
|
|
|
&data,
|
|
|
|
(GClosureNotify) watch_name_data_free_func));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
id = g_bus_watch_name_on_connection (connection,
|
|
|
|
name,
|
|
|
|
watch_name_test->watcher_flags,
|
|
|
|
name_appeared_handler,
|
|
|
|
name_vanished_handler,
|
|
|
|
&data,
|
|
|
|
(GDestroyNotify) watch_name_data_free_func);
|
|
|
|
}
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data.num_appeared, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_appeared, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Unwatch the name.
|
|
|
|
*/
|
|
|
|
g_bus_unwatch_name (id);
|
|
|
|
g_assert_cmpint (data.num_free_func, ==, 1);
|
|
|
|
|
|
|
|
/* unown the name */
|
|
|
|
g_bus_unown_name (owner_id);
|
gdbusnameowning: Fix race between connection shutdown and NameLost
As with all D-Bus signal subscriptions, it’s possible for a signal
callback to be invoked in one thread (T1) while another thread (T2) is
unsubscribing from that signal. In this case, T1 is the main thread, and
T2 is the D-Bus connection worker thread which is unsubscribing all
signals as it’s in the process of closing.
Due to this possibility, all `user_data` for signal callbacks needs to
be referenced outside the lifecycle of the code which
subscribes/unsubscribes the signal. In other words, it’s not safe to
subscribe to a signal, store the subscription ID in a struct,
unsubscribe from the signal when freeing the struct, and dereference the
struct in the signal callback. The data passed to the signal callback
has to have its own strong reference.
Instead, it’s safe to subscribe to a signal and add a strong reference
to the struct, store the subscription ID in that struct, and unsubscribe
from the signal when the last external reference to your struct is
dropped. That unsubscription should break the refcount cycle between the
signal connection and the struct, and allow the struct to be completely
freed. Only with that approach is it safe to dereference the struct in
the signal callback, if there’s any possibility that the signal might be
unsubscribed from a separate thread.
The tests need specific additional main loop cycles to completely emit
the NameLost signal callback. Ideally they need refactoring, but this
will do (1000 test cycles passed).
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Fixes: #978
2020-01-17 21:00:22 +01:00
|
|
|
g_main_loop_run (loop);
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_free_func, ==, 2);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create a watcher and then make a name be owned.
|
|
|
|
*
|
|
|
|
* This should trigger name_appeared_handler() ...
|
|
|
|
*/
|
|
|
|
/* watch the name */
|
|
|
|
data.num_appeared = 0;
|
|
|
|
data.num_vanished = 0;
|
|
|
|
data.num_free_func = 0;
|
2021-01-16 08:03:34 +01:00
|
|
|
if (watch_name_test->watch_with_closures)
|
|
|
|
{
|
|
|
|
id = g_bus_watch_name_with_closures (G_BUS_TYPE_SESSION,
|
|
|
|
name,
|
|
|
|
watch_name_test->watcher_flags,
|
|
|
|
g_cclosure_new (G_CALLBACK (name_appeared_handler),
|
|
|
|
&data,
|
|
|
|
NULL),
|
|
|
|
g_cclosure_new (G_CALLBACK (name_vanished_handler),
|
|
|
|
&data,
|
|
|
|
(GClosureNotify) watch_name_data_free_func));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
id = g_bus_watch_name (G_BUS_TYPE_SESSION,
|
|
|
|
name,
|
|
|
|
watch_name_test->watcher_flags,
|
|
|
|
name_appeared_handler,
|
|
|
|
name_vanished_handler,
|
|
|
|
&data,
|
|
|
|
(GDestroyNotify) watch_name_data_free_func);
|
|
|
|
}
|
|
|
|
|
2010-05-06 20:13:59 +02:00
|
|
|
g_assert_cmpint (data.num_appeared, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 0);
|
|
|
|
g_main_loop_run (loop);
|
2021-01-16 08:03:34 +01:00
|
|
|
if (watch_name_test->existing_service)
|
|
|
|
{
|
|
|
|
g_assert_cmpint (data.num_appeared, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_assert_cmpint (data.num_appeared, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 1);
|
|
|
|
}
|
2010-05-06 20:13:59 +02:00
|
|
|
|
2021-01-16 08:03:34 +01:00
|
|
|
if (!watch_name_test->existing_service)
|
|
|
|
{
|
|
|
|
/* own the name */
|
|
|
|
data.num_acquired = 0;
|
|
|
|
data.num_lost = 0;
|
|
|
|
data.expect_null_connection = FALSE;
|
|
|
|
owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
|
|
|
|
name,
|
|
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
|
|
w_bus_acquired_handler,
|
|
|
|
w_name_acquired_handler,
|
|
|
|
w_name_lost_handler,
|
|
|
|
&data,
|
|
|
|
(GDestroyNotify) watch_name_data_free_func);
|
|
|
|
while (data.num_acquired == 0 || data.num_appeared == 0)
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_acquired, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_appeared, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 1);
|
|
|
|
}
|
2010-05-06 20:13:59 +02:00
|
|
|
|
2021-01-16 08:03:34 +01:00
|
|
|
data.expect_null_connection = TRUE;
|
|
|
|
if (watch_name_test->existing_service)
|
|
|
|
{
|
|
|
|
data.expect_null_connection = FALSE;
|
|
|
|
stop_service (connection, &data);
|
|
|
|
}
|
|
|
|
g_object_unref (connection);
|
2010-05-06 20:13:59 +02:00
|
|
|
/*
|
|
|
|
* Nuke the bus and check that the name vanishes and is lost.
|
|
|
|
*/
|
2012-04-18 23:28:17 +02:00
|
|
|
session_bus_stop ();
|
2021-01-16 08:03:34 +01:00
|
|
|
if (!watch_name_test->existing_service)
|
|
|
|
{
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 1);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 2);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_assert_cmpint (data.num_lost, ==, 0);
|
|
|
|
g_assert_cmpint (data.num_vanished, ==, 1);
|
|
|
|
}
|
2010-05-06 20:13:59 +02:00
|
|
|
g_bus_unwatch_name (id);
|
|
|
|
g_assert_cmpint (data.num_free_func, ==, 1);
|
|
|
|
|
2021-01-16 08:03:34 +01:00
|
|
|
if (!watch_name_test->existing_service)
|
|
|
|
{
|
|
|
|
g_bus_unown_name (owner_id);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (data.num_free_func, ==, 2);
|
|
|
|
}
|
2012-04-18 23:28:17 +02:00
|
|
|
session_bus_down ();
|
2010-05-06 20:13:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_validate_names (void)
|
|
|
|
{
|
|
|
|
guint n;
|
|
|
|
static const struct
|
|
|
|
{
|
|
|
|
gboolean name;
|
|
|
|
gboolean unique;
|
|
|
|
gboolean interface;
|
|
|
|
const gchar *string;
|
|
|
|
} names[] = {
|
|
|
|
{ 1, 0, 1, "valid.well_known.name"},
|
|
|
|
{ 1, 0, 0, "valid.well-known.name"},
|
|
|
|
{ 1, 1, 0, ":valid.unique.name"},
|
|
|
|
{ 0, 0, 0, "invalid.5well_known.name"},
|
|
|
|
{ 0, 0, 0, "4invalid.5well_known.name"},
|
|
|
|
{ 1, 1, 0, ":4valid.5unique.name"},
|
|
|
|
{ 0, 0, 0, ""},
|
|
|
|
{ 1, 0, 1, "very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.name1"}, /* 255 */
|
|
|
|
{ 0, 0, 0, "very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.name12"}, /* 256 - too long! */
|
|
|
|
{ 0, 0, 0, ".starts.with.a.dot"},
|
|
|
|
{ 0, 0, 0, "contains.invalid;.characters"},
|
|
|
|
{ 0, 0, 0, "contains.inva/lid.characters"},
|
|
|
|
{ 0, 0, 0, "contains.inva[lid.characters"},
|
|
|
|
{ 0, 0, 0, "contains.inva]lid.characters"},
|
|
|
|
{ 0, 0, 0, "contains.inva_æøå_lid.characters"},
|
|
|
|
{ 1, 1, 0, ":1.1"},
|
|
|
|
};
|
|
|
|
|
|
|
|
for (n = 0; n < G_N_ELEMENTS (names); n++)
|
|
|
|
{
|
|
|
|
if (names[n].name)
|
|
|
|
g_assert (g_dbus_is_name (names[n].string));
|
|
|
|
else
|
|
|
|
g_assert (!g_dbus_is_name (names[n].string));
|
|
|
|
|
|
|
|
if (names[n].unique)
|
|
|
|
g_assert (g_dbus_is_unique_name (names[n].string));
|
|
|
|
else
|
|
|
|
g_assert (!g_dbus_is_unique_name (names[n].string));
|
|
|
|
|
|
|
|
if (names[n].interface)
|
|
|
|
g_assert (g_dbus_is_interface_name (names[n].string));
|
|
|
|
else
|
|
|
|
g_assert (!g_dbus_is_interface_name (names[n].string));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-20 14:23:24 +01:00
|
|
|
static void
|
|
|
|
assert_cmp_escaped_object_path (const gchar *s,
|
|
|
|
const gchar *correct_escaped)
|
|
|
|
{
|
|
|
|
gchar *escaped;
|
|
|
|
guint8 *unescaped;
|
|
|
|
|
|
|
|
escaped = g_dbus_escape_object_path (s);
|
|
|
|
g_assert_cmpstr (escaped, ==, correct_escaped);
|
|
|
|
|
|
|
|
g_free (escaped);
|
|
|
|
escaped = g_dbus_escape_object_path_bytestring ((const guint8 *) s);
|
|
|
|
g_assert_cmpstr (escaped, ==, correct_escaped);
|
|
|
|
|
|
|
|
unescaped = g_dbus_unescape_object_path (escaped);
|
|
|
|
g_assert_cmpstr ((const gchar *) unescaped, ==, s);
|
|
|
|
|
|
|
|
g_free (escaped);
|
|
|
|
g_free (unescaped);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_escape_object_path (void)
|
|
|
|
{
|
|
|
|
assert_cmp_escaped_object_path ("Foo42", "Foo42");
|
|
|
|
assert_cmp_escaped_object_path ("foo.bar.baz", "foo_2ebar_2ebaz");
|
|
|
|
assert_cmp_escaped_object_path ("foo_bar_baz", "foo_5fbar_5fbaz");
|
|
|
|
assert_cmp_escaped_object_path ("_", "_5f");
|
|
|
|
assert_cmp_escaped_object_path ("__", "_5f_5f");
|
|
|
|
assert_cmp_escaped_object_path ("", "_");
|
|
|
|
assert_cmp_escaped_object_path (":1.42", "_3a1_2e42");
|
|
|
|
assert_cmp_escaped_object_path ("a/b", "a_2fb");
|
|
|
|
assert_cmp_escaped_object_path (" ", "_20");
|
|
|
|
assert_cmp_escaped_object_path ("\n", "_0a");
|
|
|
|
|
|
|
|
g_assert_null (g_dbus_unescape_object_path ("_ii"));
|
|
|
|
g_assert_null (g_dbus_unescape_object_path ("döner"));
|
|
|
|
g_assert_null (g_dbus_unescape_object_path ("_00"));
|
|
|
|
g_assert_null (g_dbus_unescape_object_path ("_61"));
|
|
|
|
g_assert_null (g_dbus_unescape_object_path ("_ga"));
|
|
|
|
g_assert_null (g_dbus_unescape_object_path ("_ag"));
|
|
|
|
}
|
|
|
|
|
2010-05-06 20:13:59 +02:00
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
int
|
|
|
|
main (int argc,
|
|
|
|
char *argv[])
|
|
|
|
{
|
|
|
|
gint ret;
|
|
|
|
|
|
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
|
2012-04-18 23:28:17 +02:00
|
|
|
g_test_dbus_unset ();
|
2010-05-06 20:13:59 +02:00
|
|
|
|
|
|
|
g_test_add_func ("/gdbus/validate-names", test_validate_names);
|
|
|
|
g_test_add_func ("/gdbus/bus-own-name", test_bus_own_name);
|
2021-01-16 08:03:34 +01:00
|
|
|
g_test_add_data_func ("/gdbus/bus-watch-name",
|
|
|
|
&watch_no_closures_no_flags,
|
|
|
|
test_bus_watch_name);
|
|
|
|
g_test_add_data_func ("/gdbus/bus-watch-name-auto-start",
|
|
|
|
&watch_no_closures_flags_auto_start,
|
|
|
|
test_bus_watch_name);
|
|
|
|
g_test_add_data_func ("/gdbus/bus-watch-name-auto-start-service-exist",
|
|
|
|
&watch_no_closures_flags_auto_start_service_exist,
|
|
|
|
test_bus_watch_name);
|
|
|
|
g_test_add_data_func ("/gdbus/bus-watch-name-closures",
|
|
|
|
&watch_closures_no_flags,
|
|
|
|
test_bus_watch_name);
|
|
|
|
g_test_add_data_func ("/gdbus/bus-watch-name-closures-auto-start",
|
|
|
|
&watch_closures_flags_auto_start,
|
|
|
|
test_bus_watch_name);
|
2021-01-20 14:23:24 +01:00
|
|
|
g_test_add_func ("/gdbus/escape-object-path", test_escape_object_path);
|
2010-05-06 20:13:59 +02:00
|
|
|
ret = g_test_run();
|
|
|
|
|
|
|
|
g_main_loop_unref (loop);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|