tests: Use atomics for shared data in gdbus-connection test

D-Bus filter functions run in a worker thread. The `gdbus-connection`
test was sharing a `FilterData` struct between the main thread and the
filter function, which was occasionally (on the order of 0.01% of test
runs) causing spurious test failures due to racing on reads/writes of
`num_handled`.

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

Fixes: #480
This commit is contained in:
Philip Withnall 2020-03-17 15:20:10 +00:00
parent 0d567c784d
commit 721e385593

View File

@ -89,6 +89,7 @@ static const GDBusInterfaceVTable boo_vtable =
NULL /* _set_property */ NULL /* _set_property */
}; };
/* Runs in a worker thread. */
static GDBusMessage * static GDBusMessage *
some_filter_func (GDBusConnection *connection, some_filter_func (GDBusConnection *connection,
GDBusMessage *message, GDBusMessage *message,
@ -812,13 +813,16 @@ test_connection_signal_match_rules (void)
/* ---------------------------------------------------------------------------------------------------- */ /* ---------------------------------------------------------------------------------------------------- */
/* Accessed both from the test code and the filter function (in a worker thread)
* so all accesses must be atomic. */
typedef struct typedef struct
{ {
guint num_handled; guint num_handled; /* (atomic) */
guint num_outgoing; guint num_outgoing; /* (atomic) */
guint32 serial; guint32 serial; /* (atomic) */
} FilterData; } FilterData;
/* Runs in a worker thread. */
static GDBusMessage * static GDBusMessage *
filter_func (GDBusConnection *connection, filter_func (GDBusConnection *connection,
GDBusMessage *message, GDBusMessage *message,
@ -831,12 +835,12 @@ filter_func (GDBusConnection *connection,
if (incoming) if (incoming)
{ {
reply_serial = g_dbus_message_get_reply_serial (message); reply_serial = g_dbus_message_get_reply_serial (message);
if (reply_serial == data->serial) if (reply_serial == g_atomic_int_get (&data->serial))
data->num_handled += 1; g_atomic_int_inc (&data->num_handled);
} }
else else
{ {
data->num_outgoing += 1; g_atomic_int_inc (&data->num_outgoing);
} }
return message; return message;
@ -849,13 +853,14 @@ typedef struct
gboolean alter_outgoing; gboolean alter_outgoing;
} FilterEffects; } FilterEffects;
/* Runs in a worker thread. */
static GDBusMessage * static GDBusMessage *
other_filter_func (GDBusConnection *connection, other_filter_func (GDBusConnection *connection,
GDBusMessage *message, GDBusMessage *message,
gboolean incoming, gboolean incoming,
gpointer user_data) gpointer user_data)
{ {
FilterEffects *effects = user_data; const FilterEffects *effects = user_data;
GDBusMessage *ret; GDBusMessage *ret;
gboolean alter; gboolean alter;
@ -928,7 +933,7 @@ static void
test_connection_filter (void) test_connection_filter (void)
{ {
GDBusConnection *c; GDBusConnection *c;
FilterData data; FilterData data = { 0, 0, 0 };
GDBusMessage *m; GDBusMessage *m;
GDBusMessage *m2; GDBusMessage *m2;
GDBusMessage *r; GDBusMessage *r;
@ -939,8 +944,7 @@ test_connection_filter (void)
FilterEffects effects; FilterEffects effects;
GVariant *result; GVariant *result;
const gchar *s; const gchar *s;
guint32 serial_temp;
memset (&data, '\0', sizeof (FilterData));
session_bus_up (); session_bus_up ();
@ -960,31 +964,34 @@ test_connection_filter (void)
"GetNameOwner"); "GetNameOwner");
g_dbus_message_set_body (m, g_variant_new ("(s)", "org.freedesktop.DBus")); g_dbus_message_set_body (m, g_variant_new ("(s)", "org.freedesktop.DBus"));
error = NULL; error = NULL;
g_dbus_connection_send_message (c, m, G_DBUS_SEND_MESSAGE_FLAGS_NONE, &data.serial, &error); g_dbus_connection_send_message (c, m, G_DBUS_SEND_MESSAGE_FLAGS_NONE, &serial_temp, &error);
g_atomic_int_set (&data.serial, serial_temp);
g_assert_no_error (error); g_assert_no_error (error);
while (data.num_handled == 0) while (g_atomic_int_get (&data.num_handled) == 0)
g_thread_yield (); g_thread_yield ();
m2 = g_dbus_message_copy (m, &error); m2 = g_dbus_message_copy (m, &error);
g_assert_no_error (error); g_assert_no_error (error);
g_dbus_connection_send_message (c, m2, G_DBUS_SEND_MESSAGE_FLAGS_NONE, &data.serial, &error); g_dbus_connection_send_message (c, m2, G_DBUS_SEND_MESSAGE_FLAGS_NONE, &serial_temp, &error);
g_atomic_int_set (&data.serial, serial_temp);
g_object_unref (m2); g_object_unref (m2);
g_assert_no_error (error); g_assert_no_error (error);
while (data.num_handled == 1) while (g_atomic_int_get (&data.num_handled) == 1)
g_thread_yield (); g_thread_yield ();
m2 = g_dbus_message_copy (m, &error); m2 = g_dbus_message_copy (m, &error);
g_assert_no_error (error); g_assert_no_error (error);
g_dbus_message_set_serial (m2, data.serial); g_dbus_message_set_serial (m2, serial_temp);
/* lock the message to test PRESERVE_SERIAL flag. */ /* lock the message to test PRESERVE_SERIAL flag. */
g_dbus_message_lock (m2); g_dbus_message_lock (m2);
g_dbus_connection_send_message (c, m2, G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL, &data.serial, &error); g_dbus_connection_send_message (c, m2, G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL, &serial_temp, &error);
g_atomic_int_set (&data.serial, serial_temp);
g_object_unref (m2); g_object_unref (m2);
g_assert_no_error (error); g_assert_no_error (error);
while (data.num_handled == 2) while (g_atomic_int_get (&data.num_handled) == 2)
g_thread_yield (); g_thread_yield ();
m2 = g_dbus_message_copy (m, &error); m2 = g_dbus_message_copy (m, &error);
@ -993,6 +1000,9 @@ test_connection_filter (void)
m2, m2,
G_DBUS_SEND_MESSAGE_FLAGS_NONE, G_DBUS_SEND_MESSAGE_FLAGS_NONE,
-1, -1,
/* can't do this write atomically
* as filter_func() needs it before
* send_message_with_reply_sync() returns */
&data.serial, &data.serial,
NULL, /* GCancellable */ NULL, /* GCancellable */
&error); &error);
@ -1000,7 +1010,7 @@ test_connection_filter (void)
g_assert_no_error (error); g_assert_no_error (error);
g_assert_nonnull (r); g_assert_nonnull (r);
g_object_unref (r); g_object_unref (r);
g_assert_cmpint (data.num_handled, ==, 4); g_assert_cmpint (g_atomic_int_get (&data.num_handled), ==, 4);
g_dbus_connection_remove_filter (c, filter_id); g_dbus_connection_remove_filter (c, filter_id);
@ -1010,15 +1020,16 @@ test_connection_filter (void)
m2, m2,
G_DBUS_SEND_MESSAGE_FLAGS_NONE, G_DBUS_SEND_MESSAGE_FLAGS_NONE,
-1, -1,
&data.serial, &serial_temp,
NULL, /* GCancellable */ NULL, /* GCancellable */
&error); &error);
g_atomic_int_set (&data.serial, serial_temp);
g_object_unref (m2); g_object_unref (m2);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_nonnull (r); g_assert_nonnull (r);
g_object_unref (r); g_object_unref (r);
g_assert_cmpint (data.num_handled, ==, 4); g_assert_cmpint (g_atomic_int_get (&data.num_handled), ==, 4);
g_assert_cmpint (data.num_outgoing, ==, 4); g_assert_cmpint (g_atomic_int_get (&data.num_outgoing), ==, 4);
/* wait for service to be available */ /* wait for service to be available */
signal_handler_id = g_dbus_connection_signal_subscribe (c, signal_handler_id = g_dbus_connection_signal_subscribe (c,