summaryrefslogtreecommitdiff
path: root/gdbus
diff options
context:
space:
mode:
authorRyan Lortie <desrt@desrt.ca>2012-07-02 00:49:46 -0400
committerRyan Lortie <desrt@desrt.ca>2012-07-02 00:49:46 -0400
commit4eea8cb823c5a113d8209b04102f17f08df853d6 (patch)
tree5a8bb7d0c220e00981c0f0ba6e834ac8e1f6e584 /gdbus
parent6a675a5bdef5949e2397b0d5ca9d0c8a36ccedfd (diff)
downloaddconf-4eea8cb823c5a113d8209b04102f17f08df853d6.tar.gz
Massively reorganise the client-side
This commit represents a rather complete rethinking of DConfEngine. - the different kinds of sources are now properly abstracted. This will make landing NFS support substantially easier. - there is now substantially more internal documentation - DConfEngineMessage is gone and replaced with ordinary function calls to be implemented by the D-Bus glue code - the GDBus glue has been factored out and is now shared between the client library and GSettings - the "outstanding" queue logic from the GSettings backend is now in the engine - all changes now go through a single API that accepts a (new) DConfChangeset object. Currently this only supports the current operations (ie: setting and resetting). In the future this object will also support the directory operations required by GSettingsList and will be the basis for the new approach to implementing the 'delayed' GSettingsBackend (which will be the method by which those two concepts can co-exist). The (internal) API of the engine changed substantially. This caused the following: - the libdconf client library has been rewritten in C. Most of the complicated aspects of it (that made it more convenience to use Vala) are now gone. - during the rewrite of libdconf, the DConfClient API changed a bit to look more like a proper GObject. It now makes GIO-style use of thread-default main contexts and uses GObject signals for notifications (instead of hand-rolled callbacks). - the GSettings backend has been substantially simplified (the "outstanding" logic is gone). No externally-visible changes. - the dbus-1 backend has taken a copy of the old engine code for now until it can be ported to the new engine and sufficiently tested. No externally-visible changes. - the dconf commandline tool and dconf-editor required minor changes to adjust to the DConfClient API changes There is a substantial amount of cleaning up and finishing of work to be done. There are many stubs remaining. There are likely still a large number of bugs.
Diffstat (limited to 'gdbus')
-rw-r--r--gdbus/Makefile.am4
-rw-r--r--gdbus/dconf-gdbus-filter.c279
-rw-r--r--gdbus/dconf-gdbus-thread.c346
3 files changed, 629 insertions, 0 deletions
diff --git a/gdbus/Makefile.am b/gdbus/Makefile.am
new file mode 100644
index 0000000..3302c19
--- /dev/null
+++ b/gdbus/Makefile.am
@@ -0,0 +1,4 @@
+noinst_LIBRARIES = libdconf-gdbus.a
+INCLUDES = $(glib_CFLAGS) -I../engine -I../common
+libdconf_gdbus_a_CFLAGS = -Wall -fPIC -DPIC
+libdconf_gdbus_a_SOURCES = dconf-gdbus-thread.c
diff --git a/gdbus/dconf-gdbus-filter.c b/gdbus/dconf-gdbus-filter.c
new file mode 100644
index 0000000..a5d4400
--- /dev/null
+++ b/gdbus/dconf-gdbus-filter.c
@@ -0,0 +1,279 @@
+#include "dconf-engine.h"
+
+
+
+
+typedef struct
+{
+ gpointer data; /* either GDBusConnection or GError */
+ guint is_error;
+ guint waiting_for_serial;
+ GQueue queue;
+} ConnectionState;
+
+typedef struct
+{
+ guint32 serial;
+ DConfEngineCallHandle *handle;
+} DConfGDBusCall;
+
+static ConnectionState connections[3];
+static GMutex dconf_gdbus_lock;
+
+static GBusType
+connection_state_get_bus_type (ConnectionState *state)
+{
+ return state - connections;
+}
+
+static gboolean
+connection_state_ensure_success (ConnectionState *state,
+ GError **error)
+{
+ if (state->is_error)
+ {
+ if (error)
+ *error = g_error_copy (state->data);
+ }
+
+ return TRUE;
+}
+
+static GDBusConnection *
+connection_state_get_connection (ConnectionState *state)
+{
+ g_assert (!state->is_error);
+
+ return state->data;
+}
+
+/* This function can be slow (as compared to the one below). */
+static void
+dconf_gdbus_handle_reply (ConnectionState *state,
+ GDBusMessage *message)
+{
+ DConfEngineCallHandle *handle;
+ GError *error = NULL;
+ GVariant *body;
+
+ g_mutex_lock (&dconf_gdbus_lock);
+ {
+ DConfGDBusCall *call;
+
+ call = g_queue_pop_head (&state->queue);
+ g_assert_cmpuint (g_dbus_message_get_serial (message), ==, call->serial);
+ handle = call->handle;
+
+ g_slice_free (DConfGDBusCall, call);
+
+ call = g_queue_peek_head (&state->queue);
+ if (call)
+ g_atomic_int_set (&state->waiting_for_serial, call->serial);
+ else
+ g_atomic_int_set (&state->waiting_for_serial, -1);
+ }
+ g_mutex_unlock (&dconf_gdbus_lock);
+
+ body = g_dbus_message_get_body (message);
+
+ if (g_dbus_message_get_message_type (message) == G_DBUS_MESSAGE_TYPE_ERROR)
+ {
+ const GVariantType *first_child_type;
+ const gchar *error_message = NULL;
+
+ first_child_type = g_variant_type_first (g_variant_get_type (body));
+
+ if (g_variant_type_equal (first_child_type, G_VARIANT_TYPE_STRING))
+ g_variant_get_child (body, 0, "&s", &error_message);
+
+ error = g_dbus_error_new_for_dbus_error (g_dbus_message_get_error_name (message), error_message);
+ body = NULL;
+ }
+
+ dconf_engine_call_handle_reply (handle, body, error);
+
+ if (error)
+ g_error_free (error);
+}
+
+/* We optimise for this function being super-efficient since it gets run
+ * on every single D-Bus message in or out.
+ *
+ * We want to bail out as quickly as possible in the case that this
+ * message does not interest us. That means we should not hold locks or
+ * anything like that.
+ *
+ * In the case that this message _does_ interest us (which should be
+ * rare) we can take a lot more time.
+ */
+static GDBusMessage *
+dconf_gdbus_filter_function (GDBusConnection *connection,
+ GDBusMessage *message,
+ gboolean incoming,
+ gpointer user_data)
+{
+ ConnectionState *state = user_data;
+
+ if (incoming)
+ {
+ switch (g_dbus_message_get_message_type (message))
+ {
+ case G_DBUS_MESSAGE_TYPE_SIGNAL:
+ {
+ const gchar *interface;
+
+ interface = g_dbus_message_get_interface (message);
+ if (interface && g_str_equal (interface, "ca.desrt.dconf.Writer"))
+ dconf_engine_handle_dbus_signal (connection_state_get_bus_type (state),
+ g_dbus_message_get_sender (message),
+ g_dbus_message_get_path (message),
+ g_dbus_message_get_member (message),
+ g_dbus_message_get_body (message));
+
+ /* Others could theoretically be interested in this... */
+ }
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ case G_DBUS_MESSAGE_TYPE_ERROR:
+ if G_UNLIKELY (g_dbus_message_get_reply_serial (message) == g_atomic_int_get (&state->waiting_for_serial))
+ {
+ /* This is definitely for us. */
+ dconf_gdbus_handle_reply (state, message);
+
+ /* Nobody else should be interested in it. */
+ g_clear_object (&message);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return message;
+}
+
+static ConnectionState *
+dconf_gdbus_get_connection_state (GBusType bus_type,
+ GError **error)
+{
+ ConnectionState *state;
+
+ g_assert (bus_type < G_N_ELEMENTS (connections));
+
+ state = &connections[bus_type];
+
+ if (g_once_init_enter (&state->data))
+ {
+ GDBusConnection *connection;
+ GError *error = NULL;
+ gpointer result;
+
+ /* This will only block the first time...
+ *
+ * Optimising this away is probably not worth the effort.
+ */
+ connection = g_bus_get_sync (bus_type, NULL, &error);
+
+ if (connection)
+ {
+ g_dbus_connection_add_filter (connection, dconf_gdbus_filter_function, state, NULL);
+ result = connection;
+ state->is_error = FALSE;
+ }
+ else
+ {
+ result = error;
+ state->is_error = TRUE;
+ }
+
+ g_once_init_leave (&state->data, result);
+ }
+
+ if (!connection_state_ensure_success (state, error))
+ return FALSE;
+
+ return state;
+}
+
+gboolean
+dconf_engine_dbus_call_async_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ DConfEngineCallHandle *handle,
+ GError **error)
+{
+ ConnectionState *state;
+ GDBusMessage *message;
+ DConfGDBusCall *call;
+ gboolean success;
+
+ state = dconf_gdbus_get_connection_state (bus_type, error);
+
+ if (state == NULL)
+ return FALSE;
+
+ message = g_dbus_message_new_method_call (bus_name, object_path, interface_name, method_name);
+ g_dbus_message_set_body (message, parameters);
+
+ g_mutex_lock (&dconf_gdbus_lock);
+
+ /* We need to set the serial in two places: state->waiting_for_serial
+ * and call->serial.
+ *
+ * g_dbus_connection_send_message() only has one out_serial parameter
+ * so we can only set one of them atomically. We elect to set the
+ * waiting_for_serial because that is the one that is accessed from
+ * the filter function without holding the lock.
+ *
+ * The serial number in the call structure is only accessed after the
+ * lock is acquired which allows us to take our time setting it (for
+ * as long as we're still holding the lock).
+ *
+ * Also: the queue itself isn't accessed until after the lock is
+ * taken, so we can delay adding the call to the queue until we know
+ * that the sending of the message was successful.
+ */
+ success = g_dbus_connection_send_message (connection_state_get_connection (state), message,
+ G_DBUS_SEND_MESSAGE_FLAGS_NONE, &state->waiting_for_serial, error);
+
+ if (success)
+ {
+ call = g_slice_new (DConfGDBusCall);
+
+ call->handle = handle;
+ call->serial = state->waiting_for_serial;
+
+ g_queue_push_tail (&state->queue, call);
+ }
+
+ g_mutex_unlock (&dconf_gdbus_lock);
+
+ return success;
+}
+
+GVariant *
+dconf_engine_dbus_call_sync_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ const GVariantType *reply_type,
+ GError **error)
+{
+ ConnectionState *state;
+
+ state = dconf_gdbus_get_connection_state (bus_type, error);
+
+ if (state == NULL)
+ return NULL;
+
+ return g_dbus_connection_call_sync (connection_state_get_connection (state),
+ bus_name, object_path, interface_name, method_name, parameters, reply_type,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
+}
diff --git a/gdbus/dconf-gdbus-thread.c b/gdbus/dconf-gdbus-thread.c
new file mode 100644
index 0000000..c4a851d
--- /dev/null
+++ b/gdbus/dconf-gdbus-thread.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
+ *
+ * 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
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * 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 Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-engine.h"
+
+/* We interact with GDBus using a worker thread just for dconf.
+ *
+ * We want to have a worker thread that's not the main thread for one
+ * main reason: we don't want to have all of our incoming signals and
+ * method call replies being delivered via the default main context
+ * (which may blocked or simply not running at all).
+ *
+ * The only question is if we should have our own thread or share the
+ * GDBus worker thread. This file takes the approach that we should
+ * have our own thread. See "dconf-gdbus-filter.c" for an approach that
+ * shares the worker thread with GDBus.
+ *
+ * We gain at least one advantage here that we cannot gain any other way
+ * (including sharing a worker thread with GDBus): fast startup.
+ *
+ * The first thing that happens when GSettings comes online is a D-Bus
+ * call to establish a watch. We have to bring up the GDBusConnection.
+ * There are two ways to do that: sync and async.
+ *
+ * We can't do either of those in GDBus's worker thread (since it
+ * doesn't exist yet). We can't do async in the main thread because the
+ * user may not be running the mainloop (as is the case for the
+ * commandline tool, for example).
+ *
+ * That leaves only one option: synchronous initialisation in the main
+ * thread. That's what the "dconf-gdbus-filter" variant of this code
+ * does, and it's slower because of it.
+ *
+ * If we have our own worker thread then we can punt synchronous
+ * initialisation of the bus to it and return immediately.
+ *
+ * We also gain the advantage that the dconf worker thread and the GDBus
+ * worker thread can both be doing work at the same time. This
+ * advantage is probably quite marginal (and is likely outweighed by the
+ * cost of all the punting around of messages between threads).
+ */
+
+typedef struct
+{
+ GBusType bus_type;
+ const gchar *bus_name;
+ const gchar *object_path;
+ const gchar *interface_name;
+ const gchar *method_name;
+ GVariant *parameters;
+ DConfEngineCallHandle *handle;
+} DConfGDBusCall;
+
+static gpointer
+dconf_gdbus_worker_thread (gpointer user_data)
+{
+ GMainContext *context = user_data;
+
+ g_main_context_push_thread_default (context);
+
+ for (;;)
+ g_main_context_iteration (context, TRUE);
+
+ /* srsly, gcc? */
+ return NULL;
+}
+
+static GMainContext *
+dconf_gdbus_get_worker_context (void)
+{
+ static GMainContext *worker_context;
+
+ if (g_once_init_enter (&worker_context))
+ {
+ GMainContext *context;
+
+ context = g_main_context_new ();
+ g_thread_new ("dconf worker", dconf_gdbus_worker_thread, context);
+ g_once_init_leave (&worker_context, context);
+ }
+
+ return worker_context;
+}
+
+static void
+dconf_gdbus_signal_handler (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GBusType bus_type = GPOINTER_TO_INT (user_data);
+
+ dconf_engine_handle_dbus_signal (bus_type, sender_name, object_path, signal_name, parameters);
+}
+
+/* The code to create and initialise the GDBusConnection for a
+ * particular bus type is more complicated than it should be.
+ *
+ * The complication comes from the fact that we must call
+ * g_dbus_connection_signal_subscribe() from the thread in which the
+ * signal handler will run (which in our case is the worker thread).
+ * g_main_context_push_thread_default() attempts to acquire the context,
+ * preventing us from temporarily pushing the worker's context just for
+ * the sake of setting up the subscription.
+ *
+ * We therefore always create the bus connection from the worker thread.
+ * For requests that are already in the worker thread this is a pretty
+ * simple affair.
+ *
+ * For requests in other threads (ie: synchronous calls) we have to poke
+ * the worker to instantiate the bus for us (if it doesn't already
+ * exist). We do that by using g_main_context_invoke() to schedule a
+ * dummy request in the worker and then we wait on a GCond until we see
+ * that the bus has been created.
+ *
+ * An attempt to get a particular bus can go one of two ways:
+ *
+ * - success: we end up with a GDBusConnection.
+ *
+ * - failure: we end up with a GError.
+ *
+ * One way or another we put the result in dconf_gdbus_get_bus_data[] so
+ * that we only have one pointer value to check. We know what type of
+ * result it is by dconf_gdbus_get_bus_is_error[].
+ */
+
+static GMutex dconf_gdbus_get_bus_lock;
+static GCond dconf_gdbus_get_bus_cond;
+static gpointer dconf_gdbus_get_bus_data[5];
+static gboolean dconf_gdbus_get_bus_is_error[5];
+
+static GDBusConnection *
+dconf_gdbus_get_bus_common (GBusType bus_type,
+ const GError **error)
+{
+ if (dconf_gdbus_get_bus_is_error[bus_type])
+ {
+ *error = dconf_gdbus_get_bus_data[bus_type];
+
+ return NULL;
+ }
+
+ return dconf_gdbus_get_bus_data[bus_type];
+}
+
+static GDBusConnection *
+dconf_gdbus_get_bus_in_worker (GBusType bus_type,
+ const GError **error)
+{
+ g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_gdbus_get_bus_data));
+
+ /* We're in the worker thread and only the worker thread can ever set
+ * this variable so there is no need to take a lock.
+ */
+ if (dconf_gdbus_get_bus_data[bus_type] == NULL)
+ {
+ GDBusConnection *connection;
+ GError *error = NULL;
+ gpointer result;
+
+ connection = g_bus_get_sync (bus_type, NULL, &error);
+
+ if (connection)
+ {
+ g_dbus_connection_signal_subscribe (connection, NULL, "ca.desrt.dconf.Writer",
+ NULL, NULL, NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+ dconf_gdbus_signal_handler, GINT_TO_POINTER (bus_type), NULL);
+ dconf_gdbus_get_bus_is_error[bus_type] = FALSE;
+ result = connection;
+ }
+ else
+ {
+ dconf_gdbus_get_bus_is_error[bus_type] = TRUE;
+ result = error;
+ }
+
+ g_assert (result != NULL);
+
+ /* It's possible that another thread was waiting for us to do
+ * this on its behalf. Wake it up.
+ *
+ * The other thread cannot actually wake up until we release the
+ * mutex below so we have a guarantee that this CPU will have
+ * flushed all outstanding writes. The other CPU has to acquire
+ * the lock so it cannot have done any out-of-order reads either.
+ */
+ g_mutex_lock (&dconf_gdbus_get_bus_lock);
+ dconf_gdbus_get_bus_data[bus_type] = result;
+ g_cond_broadcast (&dconf_gdbus_get_bus_cond);
+ g_mutex_unlock (&dconf_gdbus_get_bus_lock);
+ }
+
+ return dconf_gdbus_get_bus_common (bus_type, error);
+}
+
+static void
+dconf_gdbus_method_call_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (source);
+ DConfEngineCallHandle *handle = user_data;
+ GError *error = NULL;
+ GVariant *reply;
+
+ reply = g_dbus_connection_call_finish (connection, result, &error);
+ dconf_engine_call_handle_reply (handle, reply, error);
+ g_clear_pointer (&reply, g_variant_unref);
+ g_clear_error (&error);
+}
+
+static gboolean
+dconf_gdbus_method_call (gpointer user_data)
+{
+ DConfGDBusCall *call = user_data;
+ GDBusConnection *connection;
+ const GError *error = NULL;
+
+ connection = dconf_gdbus_get_bus_in_worker (call->bus_type, &error);
+
+ if (connection)
+ g_dbus_connection_call (connection, call->bus_name, call->object_path, call->interface_name,
+ call->method_name, call->parameters, NULL, G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, dconf_gdbus_method_call_done, call->handle);
+
+ else
+ dconf_engine_call_handle_reply (call->handle, NULL, error);
+
+ g_variant_unref (call->parameters);
+ g_slice_free (DConfGDBusCall, call);
+
+ return FALSE;
+}
+
+gboolean
+dconf_engine_dbus_call_async_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ DConfEngineCallHandle *handle,
+ GError **error)
+{
+ DConfGDBusCall *call;
+
+ call = g_slice_new (DConfGDBusCall);
+ call->bus_type = bus_type;
+ call->bus_name = bus_name;
+ call->object_path = object_path;
+ call->interface_name = interface_name;
+ call->method_name = method_name;
+ call->parameters = g_variant_ref (parameters);
+ call->handle = handle;
+
+ g_main_context_invoke (dconf_gdbus_get_worker_context (), dconf_gdbus_method_call, call);
+
+ return TRUE;
+}
+
+/* Dummy function to force the bus into existence in the worker. */
+static gboolean
+dconf_gdbus_summon_bus (gpointer user_data)
+{
+ GBusType bus_type = GPOINTER_TO_INT (user_data);
+
+ dconf_gdbus_get_bus_in_worker (bus_type, NULL);
+
+ return G_SOURCE_REMOVE;
+}
+
+static GDBusConnection *
+dconf_gdbus_get_bus_for_sync (GBusType bus_type,
+ const GError **error)
+{
+ g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_gdbus_get_bus_data));
+
+ /* I'm not 100% sure we have to lock as much as we do here, but let's
+ * play it safe.
+ *
+ * This codepath is only hit on synchronous calls anyway. You're
+ * probably not doing those if you care a lot about performance.
+ */
+ g_mutex_lock (&dconf_gdbus_get_bus_lock);
+ if (dconf_gdbus_get_bus_data[bus_type] == NULL)
+ {
+ g_main_context_invoke (dconf_gdbus_get_worker_context (),
+ dconf_gdbus_summon_bus,
+ GINT_TO_POINTER (bus_type));
+
+ while (dconf_gdbus_get_bus_data[bus_type] == NULL)
+ g_cond_wait (&dconf_gdbus_get_bus_cond, &dconf_gdbus_get_bus_lock);
+ }
+ g_mutex_unlock (&dconf_gdbus_get_bus_lock);
+
+ return dconf_gdbus_get_bus_common (bus_type, error);
+}
+
+GVariant *
+dconf_engine_dbus_call_sync_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ const GVariantType *reply_type,
+ GError **error)
+{
+ const GError *inner_error = NULL;
+ GDBusConnection *connection;
+
+ connection = dconf_gdbus_get_bus_for_sync (bus_type, &inner_error);
+
+ if (connection == NULL)
+ {
+ if (error)
+ *error = g_error_copy (inner_error);
+
+ return NULL;
+ }
+
+ return g_dbus_connection_call_sync (connection, bus_name, object_path, interface_name, method_name,
+ parameters, reply_type, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
+}