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>
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).
65 const gchar
*bus_name
;
66 const gchar
*object_path
;
67 const gchar
*interface_name
;
68 const gchar
*method_name
;
70 const GVariantType
*expected_type
;
71 DConfEngineCallHandle
*handle
;
75 dconf_gdbus_worker_thread (gpointer user_data
)
77 GMainContext
*context
= user_data
;
79 g_main_context_push_thread_default (context
);
82 g_main_context_iteration (context
, TRUE
);
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
;
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
,
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
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
])
166 *error
= dconf_gdbus_get_bus_data
[bus_type
];
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
;
189 connection
= g_bus_get_sync (bus_type
, NULL
, &error
);
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
;
201 dconf_gdbus_get_bus_is_error
[bus_type
] = TRUE
;
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
);
225 dconf_gdbus_method_call_done (GObject
*source
,
226 GAsyncResult
*result
,
229 GDBusConnection
*connection
= G_DBUS_CONNECTION (source
);
230 DConfEngineCallHandle
*handle
= user_data
;
231 GError
*error
= NULL
;
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
);
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
);
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
);
255 dconf_engine_call_handle_reply (call
->handle
, NULL
, error
);
257 g_variant_unref (call
->parameters
);
258 g_slice_free (DConfGDBusCall
, call
);
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
,
273 DConfGDBusCall
*call
;
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
);
294 /* Dummy function to force the bus into existence in the worker. */
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
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
);
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
,
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
));
352 *error
= g_error_copy (inner_error
);
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
);
363 dconf_engine_dbus_init_for_testing (void)