/*
* 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, see .
*
* Author: Ryan Lortie
*/
#include "config.h"
#include "../engine/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;
const GVariantType *expected_type;
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;
/* Work around https://bugzilla.gnome.org/show_bug.cgi?id=674885
*
* This set of types is the same as the set in
* glib/gio/gdbusprivate.c:ensure_required_types(). That workaround
* is ineffective for us since we're already in the worker thread when
* we call g_bus_get_sync() and ensure_required_types() runs. So we do
* something similar here before launching the worker thread. Calling
* g_bus_get_sync() here would also be possible, but potentially would
* cause significant startup latency for every dconf user.
*/
g_type_ensure (G_TYPE_TASK);
g_type_ensure (G_TYPE_MEMORY_INPUT_STREAM);
g_type_ensure (G_TYPE_DBUS_CONNECTION_FLAGS);
g_type_ensure (G_TYPE_DBUS_CAPABILITY_FLAGS);
g_type_ensure (G_TYPE_DBUS_AUTH_OBSERVER);
g_type_ensure (G_TYPE_DBUS_CONNECTION);
g_type_ensure (G_TYPE_DBUS_PROXY);
g_type_ensure (G_TYPE_SOCKET_FAMILY);
g_type_ensure (G_TYPE_SOCKET_TYPE);
g_type_ensure (G_TYPE_SOCKET_PROTOCOL);
g_type_ensure (G_TYPE_SOCKET_ADDRESS);
g_type_ensure (G_TYPE_SOCKET);
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,
GError **error)
{
if (dconf_gdbus_get_bus_is_error[bus_type])
{
if (error)
*error = g_error_copy (dconf_gdbus_get_bus_data[bus_type]);
return NULL;
}
return g_object_ref (dconf_gdbus_get_bus_data[bus_type]);
}
static void
dconf_gdbus_bus_connection_closed (GDBusConnection *connection,
gboolean remote_peer_vanished,
GError *error,
gpointer user_data)
{
GBusType bus_type = GPOINTER_TO_INT (user_data);
dconf_engine_dbus_handle_connection_closed (connection, remote_peer_vanished, error,
&dconf_gdbus_get_bus_lock,
&dconf_gdbus_get_bus_is_error[bus_type],
&dconf_gdbus_get_bus_data[bus_type],
G_CALLBACK (dconf_gdbus_bus_connection_closed),
user_data);
}
static GDBusConnection *
dconf_gdbus_get_bus_in_worker (GBusType bus_type,
GError **error)
{
GDBusConnection *connection;
g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_gdbus_get_bus_data));
g_mutex_lock (&dconf_gdbus_get_bus_lock);
if (dconf_gdbus_get_bus_data[bus_type] == NULL)
{
GError *error = NULL;
gpointer result;
connection = g_bus_get_sync (bus_type, NULL, &error);
if (connection)
{
g_signal_connect (connection, "closed",
G_CALLBACK (dconf_gdbus_bus_connection_closed),
GINT_TO_POINTER (bus_type));
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.
*/
dconf_gdbus_get_bus_data[bus_type] = result;
}
connection = dconf_gdbus_get_bus_common (bus_type, error);
g_cond_broadcast (&dconf_gdbus_get_bus_cond);
g_mutex_unlock (&dconf_gdbus_get_bus_lock);
return connection;
}
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;
g_autoptr(GDBusConnection) connection = NULL;
g_autoptr(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, call->expected_type, 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;
GSource *source;
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_sink (parameters);
call->expected_type = dconf_engine_call_handle_get_expected_type (handle);
call->handle = handle;
source = g_idle_source_new ();
g_source_set_callback (source, dconf_gdbus_method_call, call, NULL);
g_source_attach (source, dconf_gdbus_get_worker_context ());
g_source_unref (source);
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);
g_autoptr(GDBusConnection) connection = NULL;
connection = dconf_gdbus_get_bus_in_worker (bus_type, NULL);
return G_SOURCE_REMOVE;
}
static GDBusConnection *
dconf_gdbus_get_bus_for_sync (GBusType bus_type,
GError **error)
{
g_autoptr(GDBusConnection) connection = NULL;
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);
}
connection = dconf_gdbus_get_bus_common (bus_type, error);
g_mutex_unlock (&dconf_gdbus_get_bus_lock);
return g_steal_pointer (&connection);
}
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)
{
g_autoptr(GDBusConnection) connection = NULL;
connection = dconf_gdbus_get_bus_for_sync (bus_type, error);
if (connection == NULL)
{
g_variant_unref (g_variant_ref_sink (parameters));
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);
}
#ifndef PIC
void
dconf_engine_dbus_init_for_testing (void)
{
}
#endif