Avoid use of link_whole in the gsettings backend
[dconf.git] / gdbus / dconf-gdbus-thread.c
blob8ed28b5e974cb4b56bf251f944a3135dec5e4f20
1 /*
2 * Copyright © 2010 Codethink Limited
3 * Copyright © 2012 Canonical Limited
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the licence, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 * Author: Ryan Lortie <desrt@desrt.ca>
21 #include "config.h"
23 #include "../engine/dconf-engine.h"
25 /* We interact with GDBus using a worker thread just for dconf.
27 * We want to have a worker thread that's not the main thread for one
28 * main reason: we don't want to have all of our incoming signals and
29 * method call replies being delivered via the default main context
30 * (which may blocked or simply not running at all).
32 * The only question is if we should have our own thread or share the
33 * GDBus worker thread. This file takes the approach that we should
34 * have our own thread. See "dconf-gdbus-filter.c" for an approach that
35 * shares the worker thread with GDBus.
37 * We gain at least one advantage here that we cannot gain any other way
38 * (including sharing a worker thread with GDBus): fast startup.
40 * The first thing that happens when GSettings comes online is a D-Bus
41 * call to establish a watch. We have to bring up the GDBusConnection.
42 * There are two ways to do that: sync and async.
44 * We can't do either of those in GDBus's worker thread (since it
45 * doesn't exist yet). We can't do async in the main thread because the
46 * user may not be running the mainloop (as is the case for the
47 * commandline tool, for example).
49 * That leaves only one option: synchronous initialisation in the main
50 * thread. That's what the "dconf-gdbus-filter" variant of this code
51 * does, and it's slower because of it.
53 * If we have our own worker thread then we can punt synchronous
54 * initialisation of the bus to it and return immediately.
56 * We also gain the advantage that the dconf worker thread and the GDBus
57 * worker thread can both be doing work at the same time. This
58 * advantage is probably quite marginal (and is likely outweighed by the
59 * cost of all the punting around of messages between threads).
62 typedef struct
64 GBusType bus_type;
65 const gchar *bus_name;
66 const gchar *object_path;
67 const gchar *interface_name;
68 const gchar *method_name;
69 GVariant *parameters;
70 const GVariantType *expected_type;
71 DConfEngineCallHandle *handle;
72 } DConfGDBusCall;
74 static gpointer
75 dconf_gdbus_worker_thread (gpointer user_data)
77 GMainContext *context = user_data;
79 g_main_context_push_thread_default (context);
81 for (;;)
82 g_main_context_iteration (context, TRUE);
84 /* srsly, gcc? */
85 return NULL;
88 static GMainContext *
89 dconf_gdbus_get_worker_context (void)
91 static GMainContext *worker_context;
93 if (g_once_init_enter (&worker_context))
95 GMainContext *context;
97 /* Work around https://bugzilla.gnome.org/show_bug.cgi?id=674885 */
98 g_type_ensure (G_TYPE_DBUS_CONNECTION);
99 g_type_ensure (G_TYPE_DBUS_PROXY);
101 context = g_main_context_new ();
102 g_thread_new ("dconf worker", dconf_gdbus_worker_thread, context);
103 g_once_init_leave (&worker_context, context);
106 return worker_context;
109 static void
110 dconf_gdbus_signal_handler (GDBusConnection *connection,
111 const gchar *sender_name,
112 const gchar *object_path,
113 const gchar *interface_name,
114 const gchar *signal_name,
115 GVariant *parameters,
116 gpointer user_data)
118 GBusType bus_type = GPOINTER_TO_INT (user_data);
120 dconf_engine_handle_dbus_signal (bus_type, sender_name, object_path, signal_name, parameters);
123 /* The code to create and initialise the GDBusConnection for a
124 * particular bus type is more complicated than it should be.
126 * The complication comes from the fact that we must call
127 * g_dbus_connection_signal_subscribe() from the thread in which the
128 * signal handler will run (which in our case is the worker thread).
129 * g_main_context_push_thread_default() attempts to acquire the context,
130 * preventing us from temporarily pushing the worker's context just for
131 * the sake of setting up the subscription.
133 * We therefore always create the bus connection from the worker thread.
134 * For requests that are already in the worker thread this is a pretty
135 * simple affair.
137 * For requests in other threads (ie: synchronous calls) we have to poke
138 * the worker to instantiate the bus for us (if it doesn't already
139 * exist). We do that by using g_main_context_invoke() to schedule a
140 * dummy request in the worker and then we wait on a GCond until we see
141 * that the bus has been created.
143 * An attempt to get a particular bus can go one of two ways:
145 * - success: we end up with a GDBusConnection.
147 * - failure: we end up with a GError.
149 * One way or another we put the result in dconf_gdbus_get_bus_data[] so
150 * that we only have one pointer value to check. We know what type of
151 * result it is by dconf_gdbus_get_bus_is_error[].
154 static GMutex dconf_gdbus_get_bus_lock;
155 static GCond dconf_gdbus_get_bus_cond;
156 static gpointer dconf_gdbus_get_bus_data[5];
157 static gboolean dconf_gdbus_get_bus_is_error[5];
159 static GDBusConnection *
160 dconf_gdbus_get_bus_common (GBusType bus_type,
161 const GError **error)
163 if (dconf_gdbus_get_bus_is_error[bus_type])
165 if (error)
166 *error = dconf_gdbus_get_bus_data[bus_type];
168 return NULL;
171 return dconf_gdbus_get_bus_data[bus_type];
174 static GDBusConnection *
175 dconf_gdbus_get_bus_in_worker (GBusType bus_type,
176 const GError **error)
178 g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_gdbus_get_bus_data));
180 /* We're in the worker thread and only the worker thread can ever set
181 * this variable so there is no need to take a lock.
183 if (dconf_gdbus_get_bus_data[bus_type] == NULL)
185 GDBusConnection *connection;
186 GError *error = NULL;
187 gpointer result;
189 connection = g_bus_get_sync (bus_type, NULL, &error);
191 if (connection)
193 g_dbus_connection_signal_subscribe (connection, NULL, "ca.desrt.dconf.Writer",
194 NULL, NULL, NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
195 dconf_gdbus_signal_handler, GINT_TO_POINTER (bus_type), NULL);
196 dconf_gdbus_get_bus_is_error[bus_type] = FALSE;
197 result = connection;
199 else
201 dconf_gdbus_get_bus_is_error[bus_type] = TRUE;
202 result = error;
205 g_assert (result != NULL);
207 /* It's possible that another thread was waiting for us to do
208 * this on its behalf. Wake it up.
210 * The other thread cannot actually wake up until we release the
211 * mutex below so we have a guarantee that this CPU will have
212 * flushed all outstanding writes. The other CPU has to acquire
213 * the lock so it cannot have done any out-of-order reads either.
215 g_mutex_lock (&dconf_gdbus_get_bus_lock);
216 dconf_gdbus_get_bus_data[bus_type] = result;
217 g_cond_broadcast (&dconf_gdbus_get_bus_cond);
218 g_mutex_unlock (&dconf_gdbus_get_bus_lock);
221 return dconf_gdbus_get_bus_common (bus_type, error);
224 static void
225 dconf_gdbus_method_call_done (GObject *source,
226 GAsyncResult *result,
227 gpointer user_data)
229 GDBusConnection *connection = G_DBUS_CONNECTION (source);
230 DConfEngineCallHandle *handle = user_data;
231 GError *error = NULL;
232 GVariant *reply;
234 reply = g_dbus_connection_call_finish (connection, result, &error);
235 dconf_engine_call_handle_reply (handle, reply, error);
236 g_clear_pointer (&reply, g_variant_unref);
237 g_clear_error (&error);
240 static gboolean
241 dconf_gdbus_method_call (gpointer user_data)
243 DConfGDBusCall *call = user_data;
244 GDBusConnection *connection;
245 const GError *error = NULL;
247 connection = dconf_gdbus_get_bus_in_worker (call->bus_type, &error);
249 if (connection)
250 g_dbus_connection_call (connection, call->bus_name, call->object_path, call->interface_name,
251 call->method_name, call->parameters, call->expected_type, G_DBUS_CALL_FLAGS_NONE,
252 -1, NULL, dconf_gdbus_method_call_done, call->handle);
254 else
255 dconf_engine_call_handle_reply (call->handle, NULL, error);
257 g_variant_unref (call->parameters);
258 g_slice_free (DConfGDBusCall, call);
260 return FALSE;
263 gboolean
264 dconf_engine_dbus_call_async_func (GBusType bus_type,
265 const gchar *bus_name,
266 const gchar *object_path,
267 const gchar *interface_name,
268 const gchar *method_name,
269 GVariant *parameters,
270 DConfEngineCallHandle *handle,
271 GError **error)
273 DConfGDBusCall *call;
274 GSource *source;
276 call = g_slice_new (DConfGDBusCall);
277 call->bus_type = bus_type;
278 call->bus_name = bus_name;
279 call->object_path = object_path;
280 call->interface_name = interface_name;
281 call->method_name = method_name;
282 call->parameters = g_variant_ref_sink (parameters);
283 call->expected_type = dconf_engine_call_handle_get_expected_type (handle);
284 call->handle = handle;
286 source = g_idle_source_new ();
287 g_source_set_callback (source, dconf_gdbus_method_call, call, NULL);
288 g_source_attach (source, dconf_gdbus_get_worker_context ());
289 g_source_unref (source);
291 return TRUE;
294 /* Dummy function to force the bus into existence in the worker. */
295 static gboolean
296 dconf_gdbus_summon_bus (gpointer user_data)
298 GBusType bus_type = GPOINTER_TO_INT (user_data);
300 dconf_gdbus_get_bus_in_worker (bus_type, NULL);
302 return G_SOURCE_REMOVE;
305 static GDBusConnection *
306 dconf_gdbus_get_bus_for_sync (GBusType bus_type,
307 const GError **error)
309 g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_gdbus_get_bus_data));
311 /* I'm not 100% sure we have to lock as much as we do here, but let's
312 * play it safe.
314 * This codepath is only hit on synchronous calls anyway. You're
315 * probably not doing those if you care a lot about performance.
317 g_mutex_lock (&dconf_gdbus_get_bus_lock);
318 if (dconf_gdbus_get_bus_data[bus_type] == NULL)
320 g_main_context_invoke (dconf_gdbus_get_worker_context (),
321 dconf_gdbus_summon_bus,
322 GINT_TO_POINTER (bus_type));
324 while (dconf_gdbus_get_bus_data[bus_type] == NULL)
325 g_cond_wait (&dconf_gdbus_get_bus_cond, &dconf_gdbus_get_bus_lock);
327 g_mutex_unlock (&dconf_gdbus_get_bus_lock);
329 return dconf_gdbus_get_bus_common (bus_type, error);
332 GVariant *
333 dconf_engine_dbus_call_sync_func (GBusType bus_type,
334 const gchar *bus_name,
335 const gchar *object_path,
336 const gchar *interface_name,
337 const gchar *method_name,
338 GVariant *parameters,
339 const GVariantType *reply_type,
340 GError **error)
342 const GError *inner_error = NULL;
343 GDBusConnection *connection;
345 connection = dconf_gdbus_get_bus_for_sync (bus_type, &inner_error);
347 if (connection == NULL)
349 g_variant_unref (g_variant_ref_sink (parameters));
351 if (error)
352 *error = g_error_copy (inner_error);
354 return NULL;
357 return g_dbus_connection_call_sync (connection, bus_name, object_path, interface_name, method_name,
358 parameters, reply_type, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
361 #ifndef PIC
362 void
363 dconf_engine_dbus_init_for_testing (void)
366 #endif