gdbusconnection: don't cache G_IO_ERROR_CANCELLED errors

It can cause failures for shared connection objects.

What can currently happen is this:
1. A user starts to asynchronously create a proxy object
2. A user starts to asynchronously create another proxy object

At this point, the asynchronous initialization for the two proxy objects
share the not yet initialized connection object.

3. While the shared connection objected is created, the user cancels the
   creation with the supplied cancellable from the fist proxy object.
4. initable_init caches the canceled error and marks the connection as
   initialized.
5. The initialization of the second proxy object fails with the same
   canceled error.

To avoid this, clear the error in this case and destroy any member
variables that may have been created before the creation was canceled.

This way, the initialization of the second proxy object will restart the
connection initialization and with probably succeed.

Signed-off-by: Michael Olbrich <m.olbrich@pengutronix.de>
This commit is contained in:
Michael Olbrich 2023-12-11 14:17:14 +00:00 committed by Philip Withnall
parent 3904d4c13e
commit 19a6742fc2
2 changed files with 85 additions and 1 deletions

View File

@ -2642,7 +2642,26 @@ initable_init (GInitable *initable,
g_propagate_error (error, g_error_copy (connection->initialization_error));
}
g_atomic_int_or (&connection->atomic_flags, FLAG_INITIALIZED);
/* Don't cache canceled errors. Otherwise other concurrent users of the same connection
* object will be canceled as well. */
if (g_error_matches (connection->initialization_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
if (connection->worker != NULL)
{
_g_dbus_worker_stop (connection->worker);
connection->worker = NULL;
if (alive_connections != NULL)
g_warn_if_fail (g_hash_table_remove (alive_connections, connection));
}
g_clear_error (&connection->initialization_error);
g_clear_object (&connection->stream);
g_clear_object (&connection->auth);
g_clear_object (&connection->credentials);
g_clear_pointer (&connection->guid, g_free);
connection->capabilities = 0;
}
else
g_atomic_int_or (&connection->atomic_flags, FLAG_INITIALIZED);
g_mutex_unlock (&connection->init_lock);
return ret;

View File

@ -1215,6 +1215,70 @@ test_connection_serials (void)
/* ---------------------------------------------------------------------------------------------------- */
static void
get_connection_cb_expect_cancel (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GDBusConnection *c;
GError *error;
error = NULL;
c = g_bus_get_finish (res, &error);
/* unref here to avoid timeouts when the test fails */
if (c)
g_object_unref (c);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
g_assert_null (c);
g_error_free (error);
}
static void
get_connection_cb_expect_success (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GDBusConnection *c;
GError *error;
error = NULL;
c = g_bus_get_finish (res, &error);
g_assert_no_error (error);
g_assert_nonnull (c);
g_main_loop_quit (loop);
g_object_unref (c);
}
static void
test_connection_cancel (void)
{
GCancellable *cancellable, *cancellable2;
g_test_summary ("Test that cancelling one of two racing g_bus_get() calls does not cancel the other one");
session_bus_up ();
cancellable = g_cancellable_new ();
cancellable2 = g_cancellable_new ();
g_bus_get (G_BUS_TYPE_SESSION, cancellable, get_connection_cb_expect_cancel, NULL);
g_bus_get (G_BUS_TYPE_SESSION, cancellable2, get_connection_cb_expect_success, NULL);
g_cancellable_cancel (cancellable);
g_main_loop_run (loop);
g_object_unref (cancellable);
g_object_unref (cancellable2);
session_bus_down ();
}
/* ---------------------------------------------------------------------------------------------------- */
static void
test_connection_basic (void)
{
@ -1301,6 +1365,7 @@ main (int argc,
g_test_add_func ("/gdbus/connection/signal-match-rules", test_connection_signal_match_rules);
g_test_add_func ("/gdbus/connection/filter", test_connection_filter);
g_test_add_func ("/gdbus/connection/serials", test_connection_serials);
g_test_add_func ("/gdbus/connection/cancel", test_connection_cancel);
ret = g_test_run();
g_main_loop_unref (loop);