[glib/glib-2-30] GDBusConnection: check for initialization where needed for thread-safety



commit 62d3e4f9af64b1a5e7c04da16ef1a6222fecd2f9
Author: Simon McVittie <simon mcvittie collabora co uk>
Date:   Thu Oct 20 13:12:26 2011 +0100

    GDBusConnection: check for initialization where needed for thread-safety
    
    Also document which fields require such a check in order to have correct
    threading semantics.
    
    This usage doesn't matches the GInitable documentation, which suggests
    use of a GError - but using an uninitialized GDBusConnection is
    programming error, and not usefully recoverable. (The GInitable
    documentation may have been a mistake - GNOME#662208.) Also, not all of
    the places where we need it can raise a GError.
    
    The check serves a dual purpose: it turns a non-deterministic crash into
    a deterministic critical warning, and is also a memory barrier for
    thread-safety. All of these functions dereference or return fields that
    are meant to be protected by FLAG_INITIALIZED, so they could crash or
    return an undefined value to their caller without this, if called from a
    thread that isn't the one that called initable_init() (although I can't
    think of any way to do that without encountering a memory barrier,
    undefined behaviour, or a race condition that leads to undefined
    behaviour if the non-initializing thread wins the race).
    
    One exception is that initable_init() itself makes a synchronous call.
    We deal with that by passing new internal flags up the call stack, to
    reassure g_dbus_connection_send_message_unlocked() that it can go ahead.
    
    Bug: https://bugzilla.gnome.org/show_bug.cgi?id=661689
    Bug: https://bugzilla.gnome.org/show_bug.cgi?id=661992
    Signed-off-by: Simon McVittie <simon mcvittie collabora co uk>
    Reviewed-by: David Zeuthen <davidz redhat com>

 gio/gdbusconnection.c |  139 ++++++++++++++++++++++++++++++++++++++++++++++---
 gio/gioenums.h        |    2 +
 2 files changed, 133 insertions(+), 8 deletions(-)
---
diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c
index 4200c8a..608f5c8 100644
--- a/gio/gdbusconnection.c
+++ b/gio/gdbusconnection.c
@@ -186,6 +186,17 @@ G_LOCK_DEFINE_STATIC (message_bus_lock);
 static GDBusConnection *the_session_bus = NULL;
 static GDBusConnection *the_system_bus = NULL;
 
+/* Extra pseudo-member of GDBusSendMessageFlags.
+ * Set by initable_init() to indicate that despite not being initialized yet,
+ * enough of the only-valid-after-init members are set that we can send a
+ * message, and we're being called from its thread, so no memory barrier is
+ * required before accessing them.
+ */
+#define SEND_MESSAGE_FLAGS_INITIALIZING (1<<31)
+
+/* Same as SEND_MESSAGE_FLAGS_INITIALIZING, but in GDBusCallFlags */
+#define CALL_FLAGS_INITIALIZING (1<<31)
+
 /* ---------------------------------------------------------------------------------------------------- */
 
 typedef struct
@@ -331,10 +342,16 @@ struct _GDBusConnection
    */
   gchar *machine_id;
 
-  /* The underlying stream used for communication */
+  /* The underlying stream used for communication
+   * Read-only after initable_init(), so it may be read if you either
+   * hold @init_lock or check for initialization first.
+   */
   GIOStream *stream;
 
-  /* The object used for authentication (if any) */
+  /* The object used for authentication (if any).
+   * Read-only after initable_init(), so it may be read if you either
+   * hold @init_lock or check for initialization first.
+   */
   GDBusAuth *auth;
 
   /* Set to TRUE if the connection has been closed */
@@ -343,16 +360,23 @@ struct _GDBusConnection
   /* Last serial used */
   guint32 last_serial;
 
-  /* The object used to send/receive message */
+  /* The object used to send/receive messages.
+   * Read-only after initable_init(), so it may be read if you either
+   * hold @init_lock or check for initialization first.
+   */
   GDBusWorker *worker;
 
   /* If connected to a message bus, this contains the unique name assigned to
-   * us by the bus (e.g. ":1.42")
+   * us by the bus (e.g. ":1.42").
+   * Read-only after initable_init(), so it may be read if you either
+   * hold @init_lock or check for initialization first.
    */
   gchar *bus_unique_name;
 
   /* The GUID returned by the other side if we authenticed as a client or
-   * the GUID to use if authenticating as a server
+   * the GUID to use if authenticating as a server.
+   * Read-only after initable_init(), so it may be read if you either
+   * hold @init_lock or check for initialization first.
    */
   gchar *guid;
 
@@ -398,10 +422,17 @@ struct _GDBusConnection
   /* Whether to exit on close */
   gboolean exit_on_close;
 
-  /* Capabilities negotiated during authentication */
+  /* Capabilities negotiated during authentication
+   * Read-only after initable_init(), so it may be read without holding a
+   * lock, if you check for initialization first.
+   */
   GDBusCapabilityFlags capabilities;
 
   GDBusAuthObserver *authentication_observer;
+
+  /* Read-only after initable_init(), so it may be read if you either
+   * hold @init_lock or check for initialization first.
+   */
   GCredentials *credentials;
 
   /* set to TRUE when finalizing */
@@ -465,6 +496,40 @@ G_DEFINE_TYPE_WITH_CODE (GDBusConnection, g_dbus_connection, G_TYPE_OBJECT,
                          G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
                          );
 
+/*
+ * Check that all members of @connection that can only be accessed after
+ * the connection is initialized can safely be accessed. If not,
+ * log a critical warning. This function is a memory barrier.
+ *
+ * Returns: %TRUE if initialized
+ */
+static gboolean
+check_initialized (GDBusConnection *connection)
+{
+  /* The access to @atomic_flags isn't conditional, so that this function
+   * provides a memory barrier for thread-safety even if checks are disabled.
+   * (If you don't want this stricter guarantee, you can call
+   * g_return_if_fail (check_initialized (c)).)
+   *
+   * This isn't strictly necessary now that we've decided use of an
+   * uninitialized GDBusConnection is undefined behaviour, but it seems
+   * better to be as deterministic as is feasible.
+   *
+   * (Anything that could suffer a crash from seeing undefined values
+   * must have a race condition - thread A initializes the connection while
+   * thread B calls a method without initialization, hoping that thread A will
+   * win the race - so its behaviour is undefined anyway.)
+   */
+  gint flags = g_atomic_int_get (&connection->atomic_flags);
+
+  g_return_val_if_fail (flags & FLAG_INITIALIZED, FALSE);
+
+  /* We can safely access this, due to the memory barrier above */
+  g_return_val_if_fail (connection->initialization_error == NULL, FALSE);
+
+  return TRUE;
+}
+
 static GHashTable *alive_connections = NULL;
 
 static void
@@ -980,6 +1045,11 @@ GIOStream *
 g_dbus_connection_get_stream (GDBusConnection *connection)
 {
   g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+
+  /* do not use g_return_val_if_fail(), we want the memory barrier */
+  if (!check_initialized (connection))
+    return NULL;
+
   return connection->stream;
 }
 
@@ -998,6 +1068,12 @@ void
 g_dbus_connection_start_message_processing (GDBusConnection *connection)
 {
   g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+  /* do not use g_return_val_if_fail(), we want the memory barrier */
+  if (!check_initialized (connection))
+    return;
+
+  g_assert (connection->worker != NULL);
   _g_dbus_worker_unfreeze (connection->worker);
 }
 
@@ -1032,6 +1108,11 @@ GDBusCapabilityFlags
 g_dbus_connection_get_capabilities (GDBusConnection *connection)
 {
   g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), G_DBUS_CAPABILITY_FLAGS_NONE);
+
+  /* do not use g_return_val_if_fail(), we want the memory barrier */
+  if (!check_initialized (connection))
+    return G_DBUS_CAPABILITY_FLAGS_NONE;
+
   return connection->capabilities;
 }
 
@@ -1162,6 +1243,12 @@ g_dbus_connection_flush_sync (GDBusConnection  *connection,
 
   ret = FALSE;
 
+  /* do not use g_return_val_if_fail(), we want the memory barrier */
+  if (!check_initialized (connection))
+    goto out;
+
+  g_assert (connection->worker != NULL);
+
   if (connection->closed)
     {
       g_set_error_literal (error,
@@ -1290,6 +1377,12 @@ g_dbus_connection_close (GDBusConnection     *connection,
 
   g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
 
+  /* do not use g_return_val_if_fail(), we want the memory barrier */
+  if (!check_initialized (connection))
+    return;
+
+  g_assert (connection->worker != NULL);
+
   simple = g_simple_async_result_new (G_OBJECT (connection),
                                       callback,
                                       user_data,
@@ -1439,6 +1532,18 @@ g_dbus_connection_send_message_unlocked (GDBusConnection   *connection,
   if (out_serial != NULL)
     *out_serial = 0;
 
+  /* If we're in initable_init(), don't check for being initialized, to avoid
+   * chicken-and-egg problems. initable_init() is responsible for setting up
+   * our prerequisites (mainly connection->worker), and only calling us
+   * from its own thread (so no memory barrier is needed).
+   *
+   * In the case where we're not initializing, do not use
+   * g_return_val_if_fail() - we want the memory barrier.
+   */
+  if ((flags & SEND_MESSAGE_FLAGS_INITIALIZING) == 0 &&
+      !check_initialized (connection))
+    goto out;
+
   if (connection->closed)
     {
       g_set_error_literal (error,
@@ -2467,7 +2572,7 @@ initable_init (GInitable     *initable,
                                                   "Hello",
                                                   NULL, /* parameters */
                                                   G_VARIANT_TYPE ("(s)"),
-                                                  G_DBUS_CALL_FLAGS_NONE,
+                                                  CALL_FLAGS_INITIALIZING,
                                                   -1,
                                                   NULL, /* TODO: cancellable */
                                                   &connection->initialization_error);
@@ -2865,6 +2970,11 @@ const gchar *
 g_dbus_connection_get_unique_name (GDBusConnection *connection)
 {
   g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+
+  /* do not use g_return_val_if_fail(), we want the memory barrier */
+  if (!check_initialized (connection))
+    return NULL;
+
   return connection->bus_unique_name;
 }
 
@@ -2891,6 +3001,11 @@ GCredentials *
 g_dbus_connection_get_peer_credentials (GDBusConnection *connection)
 {
   g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+
+  /* do not use g_return_val_if_fail(), we want the memory barrier */
+  if (!check_initialized (connection))
+    return NULL;
+
   return connection->credentials;
 }
 
@@ -5235,6 +5350,7 @@ g_dbus_connection_call_sync_internal (GDBusConnection         *connection,
   GDBusMessage *reply;
   GVariant *result;
   GError *local_error;
+  GDBusSendMessageFlags send_flags;
 
   message = NULL;
   reply = NULL;
@@ -5286,9 +5402,16 @@ g_dbus_connection_call_sync_internal (GDBusConnection         *connection,
     }
 
   local_error = NULL;
+
+  send_flags = G_DBUS_SEND_MESSAGE_FLAGS_NONE;
+
+  /* translate from one flavour of flags to another... */
+  if (flags & CALL_FLAGS_INITIALIZING)
+    send_flags |= SEND_MESSAGE_FLAGS_INITIALIZING;
+
   reply = g_dbus_connection_send_message_with_reply_sync (connection,
                                                           message,
-							  G_DBUS_SEND_MESSAGE_FLAGS_NONE,
+                                                          send_flags,
                                                           timeout_msec,
                                                           NULL, /* volatile guint32 *out_serial */
                                                           cancellable,
diff --git a/gio/gioenums.h b/gio/gioenums.h
index 2a23597..2d4af44 100644
--- a/gio/gioenums.h
+++ b/gio/gioenums.h
@@ -1053,6 +1053,7 @@ typedef enum {
   G_DBUS_CALL_FLAGS_NONE = 0,
   G_DBUS_CALL_FLAGS_NO_AUTO_START = (1<<0)
 } GDBusCallFlags;
+/* (1<<31) is reserved for internal use by GDBusConnection, do not use it. */
 
 /**
  * GDBusMessageType:
@@ -1208,6 +1209,7 @@ typedef enum
   G_DBUS_SEND_MESSAGE_FLAGS_NONE = 0,
   G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL = (1<<0)
 } GDBusSendMessageFlags;
+/* (1<<31) is reserved for internal use by GDBusConnection, do not use it. */
 
 /**
  * GCredentialsType:



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]