tests/: convert path test to gtester
[dconf.git] / engine / dconf-engine.c
blob3c9d9f52ca791b848c9eba658b5e0591e4448d72
1 /*
2 * Copyright © 2010 Codethink Limited
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the licence, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
19 * Author: Ryan Lortie <desrt@desrt.ca>
22 #define _XOPEN_SOURCE 600
23 #include "dconf-engine.h"
25 #include <gvdb-reader.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32 #include <sys/mman.h>
34 #include "dconf-engine-profile.h"
36 /* The engine has zero or more sources.
38 * If it has zero sources then things are very uninteresting. Nothing
39 * is writable, nothing will ever be written and reads will always
40 * return NULL.
42 * There are two interesting cases when there is a non-zero number of
43 * sources. Writing only ever occurs to the first source, if at all.
44 * Non-first sources are never writable.
46 * The first source may or may not be writable. In the usual case the
47 * first source is the one in the user's home directory and is writable,
48 * but it may be that the profile was setup for read-only access to
49 * system sources only.
51 * In the case that the first source is not writable (and therefore
52 * there are no writable sources), is_writable() will always return
53 * FALSE and no writes will ever be performed.
55 * It's possible to request changes in three ways:
57 * - synchronous: the D-Bus message is immediately sent to the
58 * dconf service and we block until we receive the reply. The change
59 * signal will follow soon thereafter (when we receive the signal on
60 * D-Bus).
62 * - asynchronous: typical asynchronous operation: we send the request
63 * and return immediately, notifying using a callback when the
64 * request is completed (and the new value is in the database). The
65 * change signal follows in the same way as with synchronous.
67 * - fast: we record the value locally and signal the change, returning
68 * immediately, as if the value is already in the database (from the
69 * viewpoint of the local process). We keep note of the new value
70 * locally until the service has confirmed that the write was
71 * successful. If the write fails, we emit a change signal. From
72 * the view of the program it looks like the value was successfully
73 * changed but then quickly changed back again by some external
74 * agent.
76 * In fast mode we have to do some management of the queue. If we
77 * immediately put all requests "in flight" then we can end up in a
78 * situation where the application writes many values for the same key
79 * and the service is kept (needlessly) busy writing over and over to
80 * the same key for some time after the requests stop coming in.
82 * If we limit the number of in-flight requests and put the other ones
83 * into a pending queue then we can perform merging of similar changes.
84 * If we notice that an item in the pending queue writes to the same
85 * keys as the newly-added request then we can simply drop the existing
86 * request (since its effect will be nullified by the new request).
88 * We want to keep the number of in-flight requests low in order to
89 * maximise our chance of dropping pending items, but we probably want
90 * it higher than 1 so that we can pipeline to hide latency.
92 * In order to minimise complexity, all changes go first to the pending
93 * queue. Changes are dispatched from the pending queue (and moved to
94 * the in-flight queue) when the number of requests in-flight is lower
95 * than the maximum.
97 * For both 'in_flight' and 'pending' queues we push to the tail and pop
98 * from the head. This puts the first operation on the head and the
99 * most recent operation on the tail.
101 * Since new operation go first to the pending queue, we find the most
102 * recent operations at the tail of that queue. Since we want to return
103 * the most-recently written value, we therefore scan for values
104 * starting at the tail of the pending queue and ending at the head of
105 * the in-flight queue.
107 * NB: I tell a lie. Async is not supported yet.
109 * Notes about threading:
111 * The engine is oblivious to threads and main contexts.
113 * What this means is that the engine has no interaction with GMainLoop
114 * and will not schedule idles or anything of the sort. All calls made
115 * by the engine to the client library will be made in response to
116 * incoming method calls, from the same thread as the incoming call.
118 * If dconf_engine_call_handle_reply() or
119 * dconf_engine_handle_dbus_signal() are called from 'exotic' threads
120 * (as will often be the case) then the resulting calls to
121 * dconf_engine_change_notify() will come from the same thread. That's
122 * left for the client library to deal with.
124 * All that said, the engine is completely threadsafe. The client
125 * library can call any method from any thread at any time -- as long as
126 * it is willing to deal with receiving the change notifies in those
127 * threads.
129 * Thread-safety is implemented using two locks.
131 * The first lock (sources_lock) protects the sources. Although the
132 * sources are only ever read from, it is necessary to lock them because
133 * it is not safe to read during a refresh (when the source is being
134 * closed and reopened). Accordingly, sources_lock need only be
135 * acquired when accessing the parts of the sources that are subject to
136 * change as a result of refreshes; the static parts (like bus type,
137 * object path, etc) can be accessed without holding the lock. The
138 * 'sources' array itself (and 'n_sources') are set at construction and
139 * never change after that.
141 * The second lock (queue_lock) protects the various queues that are
142 * used to implement the "fast" writes described above.
144 * If both locks are held at the same time thne the sources lock must
145 * have been acquired first.
148 #define MAX_IN_FLIGHT 2
150 static GSList *dconf_engine_global_list;
151 static GMutex dconf_engine_global_lock;
153 struct _DConfEngine
155 gpointer user_data; /* Set at construct time */
156 GDestroyNotify free_func;
157 gint ref_count;
159 GMutex sources_lock; /* This lock is for the sources (ie: refreshing) and state. */
160 guint64 state; /* Counter that changes every time a source is refreshed. */
161 DConfEngineSource **sources; /* Array never changes, but each source changes internally. */
162 gint n_sources;
164 GMutex queue_lock; /* This lock is for pending, in_flight */
165 GQueue pending; /* DConfChangeset */
166 GQueue in_flight; /* DConfChangeset */
168 gchar *last_handled; /* reply tag from last item in in_flight */
171 /* When taking the sources lock we check if any of the databases have
172 * had updates.
174 * Anything that is accessing the database (even only reading) needs to
175 * be holding the lock (since refreshes could be happening in another
176 * thread), so this makes sense.
178 * We could probably optimise this to avoid checking some databases in
179 * certain cases (ie: we do not need to check the user's database when
180 * we are only interested in checking writability) but this works well
181 * enough for now and is less prone to errors.
183 * We could probably change to a reader/writer situation that is only
184 * holding the write lock when actually making changes during a refresh
185 * but the engine is probably only ever really in use by two threads at
186 * a given time (main thread doing reads, DBus worker thread clearing
187 * the queue) so it seems unlikely that lock contention will become an
188 * issue.
190 * If it does, we can revisit this...
192 static void
193 dconf_engine_acquire_sources (DConfEngine *engine)
195 gint i;
197 g_mutex_lock (&engine->sources_lock);
199 for (i = 0; i < engine->n_sources; i++)
200 if (dconf_engine_source_refresh (engine->sources[i]))
201 engine->state++;
204 static void
205 dconf_engine_release_sources (DConfEngine *engine)
207 g_mutex_unlock (&engine->sources_lock);
210 static void
211 dconf_engine_lock_queues (DConfEngine *engine)
213 g_mutex_lock (&engine->queue_lock);
216 static void
217 dconf_engine_unlock_queues (DConfEngine *engine)
219 g_mutex_unlock (&engine->queue_lock);
222 DConfEngine *
223 dconf_engine_new (gpointer user_data,
224 GDestroyNotify free_func)
226 DConfEngine *engine;
227 gint i;
229 engine = g_slice_new0 (DConfEngine);
230 engine->user_data = user_data;
231 engine->free_func = free_func;
232 engine->ref_count = 1;
234 g_mutex_init (&engine->sources_lock);
235 g_mutex_init (&engine->queue_lock);
237 engine->sources = dconf_engine_profile_get_default (&engine->n_sources);
239 for (i = 0; i < engine->n_sources; i++)
240 if (!dconf_engine_source_init (engine->sources[i]))
241 g_assert_not_reached ();
243 g_mutex_lock (&dconf_engine_global_lock);
244 dconf_engine_global_list = g_slist_prepend (dconf_engine_global_list, engine);
245 g_mutex_unlock (&dconf_engine_global_lock);
247 return engine;
250 void
251 dconf_engine_unref (DConfEngine *engine)
253 if (g_atomic_int_dec_and_test (&engine->ref_count))
255 gint i;
257 /* We just dropped our refcount to zero, but we're still in the
258 * dconf_engine_global_list.
260 * If a signal arrives at this exact instant and the signal
261 * handler beats us to the lock then the refcount will be
262 * increased again.
264 * Acquire the lock and then double-check that the refcount is
265 * still zero before actually doing the remove. If it's non-zero
266 * then the signal handler grabbed a ref and will call unref()
267 * later.
269 g_mutex_lock (&dconf_engine_global_lock);
270 if (g_atomic_int_get (&engine->ref_count) != 0)
272 g_mutex_unlock (&dconf_engine_global_lock);
273 return;
276 /* It's possible that another thread grabbed a reference at the
277 * last minute and dropped it back to zero again (thus causing the
278 * above check to pass). In that case, however, the other thread
279 * will have also dropped the refcount from 1 to 0 and be inside
280 * of the dec-and-test above.
282 * We can only have one of the two threads doing the freeing of
283 * the data, so we have a simple rule: the thread that removes the
284 * engine from the global list is the one that does the free.
285 * Since this operation is performed under mutex we can be sure
286 * that only one thread will win.
288 if (!g_slist_find (dconf_engine_global_list, engine))
290 g_mutex_unlock (&dconf_engine_global_lock);
291 return;
294 dconf_engine_global_list = g_slist_remove (dconf_engine_global_list, engine);
295 g_mutex_unlock (&dconf_engine_global_lock);
297 g_mutex_clear (&engine->sources_lock);
298 g_mutex_clear (&engine->queue_lock);
300 g_free (engine->last_handled);
302 for (i = 0; i < engine->n_sources; i++)
303 dconf_engine_source_free (engine->sources[i]);
305 g_free (engine->sources);
307 if (engine->free_func)
308 engine->free_func (engine->user_data);
310 g_slice_free (DConfEngine, engine);
314 static DConfEngine *
315 dconf_engine_ref (DConfEngine *engine)
317 g_atomic_int_inc (&engine->ref_count);
319 return engine;
322 guint64
323 dconf_engine_get_state (DConfEngine *engine)
325 guint64 state;
327 dconf_engine_acquire_sources (engine);
328 state = engine->state;
329 dconf_engine_release_sources (engine);
331 return state;
334 static gboolean
335 dconf_engine_is_writable_internal (DConfEngine *engine,
336 const gchar *key)
338 gint i;
340 /* We must check several things:
342 * - we have at least one source
344 * - the first source is writable
346 * - the key is not locked in a non-writable (ie: non-first) source
348 if (engine->n_sources == 0)
349 return FALSE;
351 if (engine->sources[0]->writable == FALSE)
352 return FALSE;
354 /* Ignore locks in the first source.
356 * Either it is writable and therefore ignoring locks is the right
357 * thing to do, or it's non-writable and we caught that case above.
359 for (i = 1; i < engine->n_sources; i++)
360 if (engine->sources[i]->locks && gvdb_table_has_value (engine->sources[i]->locks, key))
361 return FALSE;
363 return TRUE;
366 gboolean
367 dconf_engine_is_writable (DConfEngine *engine,
368 const gchar *key)
370 gboolean writable;
372 dconf_engine_acquire_sources (engine);
373 writable = dconf_engine_is_writable_internal (engine, key);
374 dconf_engine_release_sources (engine);
376 return writable;
379 static gboolean
380 dconf_engine_find_key_in_queue (GQueue *queue,
381 const gchar *key,
382 GVariant **value)
384 GList *node;
386 /* Tail to head... */
387 for (node = g_queue_peek_tail_link (queue); node; node = node->prev)
388 if (dconf_changeset_get (node->data, key, value))
389 return TRUE;
391 return FALSE;
394 GVariant *
395 dconf_engine_read (DConfEngine *engine,
396 DConfChangesetList *read_through,
397 const gchar *key)
399 GVariant *value = NULL;
400 gint lock_level = 0;
401 gint i;
403 dconf_engine_acquire_sources (engine);
405 /* There are a number of situations that this function has to deal
406 * with and they interact in unusual ways. We attempt to write the
407 * rules for all cases here:
409 * With respect to the steady-state condition with no locks:
411 * This is the case where there are no changes queued, no
412 * read_through and no locks.
414 * The value returned is the one from the lowest-index source that
415 * contains that value.
417 * With respect to locks:
419 * If a lock is present (except in source #0 where it is ignored)
420 * then we will only return a value found in the source where the
421 * lock was present, or a higher-index source (following the normal
422 * rule that sources with lower indexes take priority).
424 * This statement includes read_through and queued changes. If a
425 * lock is found, we will ignore those.
427 * With respect to read_through and queued changed:
429 * We only consider read_through and queued changes in the event
430 * that we have a writable source. This will possibly cause us to
431 * ignore read_through and will have no real effect on the queues
432 * (since they will be empty anyway if we have no writable source).
434 * We only consider read_through and queued changes in the event
435 * that we have not found any locks.
437 * If there is a non-NULL value found in read_through or the queued
438 * changes then we will return that value.
440 * If there is a NULL value (ie: a reset) found in read_through or
441 * the queued changes then we will only ignore any value found in
442 * the first source (which must be writable, or else we would not
443 * have been considering read_through and the queues). This is
444 * consistent with the fact that a reset will unset any value found
445 * in this source but will not affect values found in lower sources.
447 * Put another way: if a non-writable source contains a value for a
448 * particular key then it is impossible for this function to return
449 * NULL.
451 * We implement the above rules as follows. We have three state
452 * tracking variables:
454 * - lock_level: records if and where we found a lock
456 * - found_key: records if we found the key in any queue
458 * - value: records the value of the found key (NULL for resets)
460 * We take these steps:
462 * 1. check for lockdown. If we find a lock then we prevent any
463 * other sources (including read_through and pending/in-flight)
464 * from affecting the value of the key.
466 * We record the result of this in the lock_level variable. Zero
467 * means that no locks were found. Non-zero means that a lock was
468 * found in the source with the index given by the variable.
470 * 2. check the uncommited changes in the read_through list as the
471 * highest priority. This is only done if we have a writable
472 * source and no locks were found.
474 * If we found an entry in the read_through then we set
475 * 'found_key' to TRUE and set 'value' to the value that we found
476 * (which will be NULL in the case of finding a reset request).
478 * 3. check our pending and in-flight "fast" changes (in that order).
479 * This is only done if we have a writable source and no locks
480 * were found. It is also only done if we did not find the key in
481 * the read_through.
483 * 4. check the first source, if there is one.
485 * This is only done if 'found_key' is FALSE. If 'found_key' is
486 * TRUE then it means that the first database was writable and we
487 * either found a value that will replace it (value != NULL) or
488 * found a pending reset (value == NULL) that will unset it.
490 * We only actually do this step if we have a writable first
491 * source and no locks found, otherwise we just let step 5 do all
492 * the checking.
494 * 5. check the remaining sources.
496 * We do this until we have value != NULL. Even if found_key was
497 * TRUE, the reset that was requested will not have affected the
498 * lower-level databases.
501 /* Step 1. Check for locks.
503 * Note: i > 0 (strictly). Ignore locks for source #0.
505 for (i = engine->n_sources - 1; i > 0; i--)
506 if (engine->sources[i]->locks && gvdb_table_has_value (engine->sources[i]->locks, key))
508 lock_level = i;
509 break;
512 /* Only do steps 2 to 4 if we have no locks and we have a writable source. */
513 if (!lock_level && engine->n_sources != 0 && engine->sources[0]->writable)
515 gboolean found_key = FALSE;
517 /* Step 2. Check read_through. */
518 if (read_through)
519 found_key = dconf_engine_find_key_in_queue (&read_through->queue, key, &value);
521 /* Step 3. Check queued changes if we didn't find it in read_through.
523 * NB: We may want to optimise this to avoid taking the lock in
524 * the case that we know both queues are empty.
526 if (!found_key)
528 dconf_engine_lock_queues (engine);
530 /* Check the pending queue first because those were submitted
531 * more recently.
533 found_key = dconf_engine_find_key_in_queue (&engine->pending, key, &value) ||
534 dconf_engine_find_key_in_queue (&engine->in_flight, key, &value);
536 dconf_engine_unlock_queues (engine);
539 /* Step 4. Check the first source. */
540 if (!found_key && engine->sources[0]->values)
541 value = gvdb_table_get_value (engine->sources[0]->values, key);
543 /* We already checked source #0 (or ignored it, as appropriate).
545 * Abuse the lock_level variable to get step 5 to skip this one.
547 lock_level = 1;
550 /* Step 5. Check the remaining sources, until value != NULL. */
551 for (i = lock_level; value == NULL && i < engine->n_sources; i++)
553 if (engine->sources[i]->values == NULL)
554 continue;
556 if ((value = gvdb_table_get_value (engine->sources[i]->values, key)))
557 break;
560 dconf_engine_release_sources (engine);
562 return value;
565 gchar **
566 dconf_engine_list (DConfEngine *engine,
567 const gchar *dir,
568 gint *length)
570 GHashTable *results;
571 GHashTableIter iter;
572 gchar **list;
573 gint n_items;
574 gpointer key;
575 gint i;
577 /* This function is unreliable in the presence of pending changes.
578 * Here's why:
580 * Consider the case that we list("/a/") and a pending request has a
581 * reset request recorded for "/a/b/c". The question of if "b/"
582 * should appear in the output rests on if "/a/b/d" also exists.
584 * Put another way: If "/a/b/c" is the only key in "/a/b/" then
585 * resetting it would mean that "/a/b/" stops existing (and we should
586 * not include it in the output). If there are others keys then it
587 * will continue to exist and we should include it.
589 * Instead of trying to sort this out, we just ignore the pending
590 * requests and report what the on-disk file says.
593 results = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
595 dconf_engine_acquire_sources (engine);
597 for (i = 0; i < engine->n_sources; i++)
599 gchar **partial_list;
600 gint j;
602 partial_list = gvdb_table_list (engine->sources[i]->values, dir);
604 if (partial_list != NULL)
606 for (j = 0; partial_list[j]; j++)
607 /* Steal the keys from the list. */
608 g_hash_table_add (results, partial_list[j]);
610 /* Free only the list. */
611 g_free (partial_list);
615 dconf_engine_release_sources (engine);
617 n_items = g_hash_table_size (results);
618 list = g_new (gchar *, n_items + 1);
620 i = 0;
621 g_hash_table_iter_init (&iter, results);
622 while (g_hash_table_iter_next (&iter, &key, NULL))
624 g_hash_table_iter_steal (&iter);
625 list[i++] = key;
627 list[i] = NULL;
628 g_assert_cmpint (i, ==, n_items);
630 if (length)
631 *length = n_items;
633 g_hash_table_unref (results);
635 return list;
638 typedef void (* DConfEngineCallHandleCallback) (DConfEngine *engine,
639 gpointer handle,
640 GVariant *parameter,
641 const GError *error);
643 struct _DConfEngineCallHandle
645 DConfEngine *engine;
646 DConfEngineCallHandleCallback callback;
647 const GVariantType *expected_reply;
650 static gpointer
651 dconf_engine_call_handle_new (DConfEngine *engine,
652 DConfEngineCallHandleCallback callback,
653 const GVariantType *expected_reply,
654 gsize size)
656 DConfEngineCallHandle *handle;
658 g_assert (engine != NULL);
659 g_assert (callback != NULL);
660 g_assert (size >= sizeof (DConfEngineCallHandle));
662 handle = g_malloc0 (size);
663 handle->engine = dconf_engine_ref (engine);
664 handle->callback = callback;
665 handle->expected_reply = expected_reply;
667 return handle;
670 void
671 dconf_engine_call_handle_reply (DConfEngineCallHandle *handle,
672 GVariant *parameter,
673 const GError *error)
675 if (handle == NULL)
676 return;
678 (* handle->callback) (handle->engine, handle, parameter, error);
681 static void
682 dconf_engine_call_handle_free (DConfEngineCallHandle *handle)
684 dconf_engine_unref (handle->engine);
685 g_free (handle);
688 /* returns floating */
689 static GVariant *
690 dconf_engine_make_match_rule (DConfEngineSource *source,
691 const gchar *path)
693 GVariant *params;
694 gchar *rule;
696 rule = g_strdup_printf ("type='signal',"
697 "interface='ca.desrt.dconf.Writer',"
698 "path='%s',"
699 "arg0path='%s'",
700 source->object_path,
701 path);
703 params = g_variant_new ("(s)", rule);
705 g_free (rule);
707 return params;
710 typedef struct
712 DConfEngineCallHandle handle;
714 guint64 state;
715 gint pending;
716 } OutstandingWatch;
718 static void
719 dconf_engine_watch_established (DConfEngine *engine,
720 gpointer handle,
721 GVariant *reply,
722 const GError *error)
724 OutstandingWatch *ow = handle;
726 /* ignore errors */
728 if (--ow->pending)
729 /* more on the way... */
730 return;
732 if (ow->state != dconf_engine_get_state (engine))
734 /* Our recorded state does not match the current state. Something
735 * must have changed while our watch requests were on the wire.
737 * We don't know what changed, so we can just say that potentially
738 * everything changed. This case is very rare, anyway...
740 dconf_engine_change_notify (engine, "/", NULL, engine->user_data, NULL);
743 dconf_engine_call_handle_free (handle);
746 void
747 dconf_engine_watch_fast (DConfEngine *engine,
748 const gchar *path)
750 OutstandingWatch *ow;
751 gint i;
753 if (engine->n_sources == 0)
754 return;
756 /* It's possible (although rare) that the dconf database could change
757 * while our match rule is on the wire.
759 * Since we returned immediately (suggesting to the user that the
760 * watch was already established) we could have a race.
762 * To deal with this, we use the current state counter to ensure that nothing
763 * changes while the watch requests are on the wire.
765 ow = dconf_engine_call_handle_new (engine, dconf_engine_watch_established,
766 G_VARIANT_TYPE_UNIT, sizeof (OutstandingWatch));
767 ow->state = dconf_engine_get_state (engine);
768 ow->pending = engine->n_sources;
770 for (i = 0; i < engine->n_sources; i++)
771 dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
772 "/org/freedesktop/DBus", "org.freedesktop.DBus", "AddMatch",
773 dconf_engine_make_match_rule (engine->sources[i], path),
774 &ow->handle, NULL);
777 void
778 dconf_engine_unwatch_fast (DConfEngine *engine,
779 const gchar *path)
781 gint i;
783 for (i = 0; i < engine->n_sources; i++)
784 dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
785 "/org/freedesktop/DBus", "org.freedesktop.DBus", "RemoveMatch",
786 dconf_engine_make_match_rule (engine->sources[i], path), NULL, NULL);
789 static void
790 dconf_engine_handle_match_rule_sync (DConfEngine *engine,
791 const gchar *method_name,
792 const gchar *path)
794 gint i;
796 /* We need not hold any locks here because we are only touching static
797 * things: the number of sources, and static properties of each source
798 * itself.
800 * This function silently ignores all errors.
803 for (i = 0; i < engine->n_sources; i++)
805 GVariant *result;
807 result = dconf_engine_dbus_call_sync_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
808 "/org/freedesktop/DBus", "org.freedesktop.DBus", method_name,
809 dconf_engine_make_match_rule (engine->sources[i], path),
810 G_VARIANT_TYPE_UNIT, NULL);
812 if (result)
813 g_variant_unref (result);
817 void
818 dconf_engine_watch_sync (DConfEngine *engine,
819 const gchar *path)
821 dconf_engine_handle_match_rule_sync (engine, "AddMatch", path);
824 void
825 dconf_engine_unwatch_sync (DConfEngine *engine,
826 const gchar *path)
828 dconf_engine_handle_match_rule_sync (engine, "RemoveMatch", path);
831 typedef struct
833 DConfEngineCallHandle handle;
835 DConfChangeset *change;
836 } OutstandingChange;
838 static GVariant *
839 dconf_engine_prepare_change (DConfEngine *engine,
840 DConfChangeset *change)
842 GVariant *serialised;
844 serialised = dconf_changeset_serialise (change);
846 return g_variant_new_from_data (G_VARIANT_TYPE ("(ay)"),
847 g_variant_get_data (serialised), g_variant_get_size (serialised), TRUE,
848 (GDestroyNotify) g_variant_unref, g_variant_ref_sink (serialised));
851 /* This function promotes changes from the pending queue to the
852 * in-flight queue by sending the appropriate D-Bus message.
854 * Of course, this is only possible when there are pending items and
855 * room in the in-flight queue. For this reason, this function gets
856 * called in two situations:
858 * - an item has been added to the pending queue (due to an API call)
860 * - an item has been removed from the inflight queue (due to a D-Bus
861 * reply having been received)
863 * It will move a maximum of one item.
865 static void dconf_engine_manage_queue (DConfEngine *engine);
867 static void
868 dconf_engine_emit_changes (DConfEngine *engine,
869 DConfChangeset *changeset)
871 const gchar *prefix;
872 const gchar * const *changes;
874 if (dconf_changeset_describe (changeset, &prefix, &changes, NULL))
875 dconf_engine_change_notify (engine, prefix, changes, NULL, engine->user_data);
878 static void
879 dconf_engine_change_completed (DConfEngine *engine,
880 gpointer handle,
881 GVariant *reply,
882 const GError *error)
884 OutstandingChange *oc = handle;
885 DConfChangeset *expected;
887 dconf_engine_lock_queues (engine);
889 /* D-Bus guarantees ordered delivery of messages.
891 * The dconf-service handles requests in-order.
893 * The reply we just received should therefore be at the head of
894 * our 'in flight' queue.
896 expected = g_queue_pop_head (&engine->in_flight);
897 g_assert (expected && oc->change == expected);
899 /* We just popped a change from the in-flight queue, possibly
900 * making room for another to be added. Check that.
902 dconf_engine_manage_queue (engine);
903 dconf_engine_unlock_queues (engine);
905 /* Deal with the reply we got. */
906 if (reply)
908 /* The write worked.
910 * We already sent a change notification for this item when we
911 * added it to the pending queue and we don't want to send another
912 * one again. At the same time, it's very likely that we're just
913 * about to receive a change signal from the service.
915 * The tag sent as part of the reply to the Change call will be
916 * the same tag as on the change notification signal. Record that
917 * tag so that we can ignore the signal when it comes.
919 * last_handled is only ever touched from the worker thread
921 g_free (engine->last_handled);
922 g_variant_get (reply, "(s)", &engine->last_handled);
925 if (error)
927 /* Some kind of unexpected failure occured while attempting to
928 * commit the change.
930 * There's not much we can do here except to drop our local copy
931 * of the change (and notify that it is gone) and print the error
932 * message as a warning.
934 g_warning ("failed to commit changes to dconf: %s", error->message);
935 dconf_engine_emit_changes (engine, oc->change);
938 dconf_changeset_unref (oc->change);
939 dconf_engine_call_handle_free (handle);
942 static void
943 dconf_engine_manage_queue (DConfEngine *engine)
945 if (!g_queue_is_empty (&engine->pending) && g_queue_get_length (&engine->in_flight) < MAX_IN_FLIGHT)
947 OutstandingChange *oc;
948 GVariant *parameters;
950 oc = dconf_engine_call_handle_new (engine, dconf_engine_change_completed,
951 G_VARIANT_TYPE ("(s)"), sizeof (OutstandingChange));
953 oc->change = g_queue_pop_head (&engine->pending);
955 parameters = dconf_engine_prepare_change (engine, oc->change);
957 dconf_engine_dbus_call_async_func (engine->sources[0]->bus_type,
958 engine->sources[0]->bus_name,
959 engine->sources[0]->object_path,
960 "ca.desrt.dconf.Writer", "Change",
961 parameters, &oc->handle, NULL);
963 g_queue_push_tail (&engine->in_flight, oc->change);
967 static gboolean
968 dconf_engine_is_writable_changeset_predicate (const gchar *key,
969 GVariant *value,
970 gpointer user_data)
972 DConfEngine *engine = user_data;
974 return dconf_engine_is_writable_internal (engine, key);
977 static gboolean
978 dconf_engine_changeset_changes_only_writable_keys (DConfEngine *engine,
979 DConfChangeset *changeset,
980 GError **error)
982 gboolean success = TRUE;
984 dconf_engine_acquire_sources (engine);
986 if (!dconf_changeset_all (changeset, dconf_engine_is_writable_changeset_predicate, engine))
988 g_set_error_literal (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE,
989 "The operation attempted to modify one or more non-writable keys");
990 success = FALSE;
993 dconf_engine_release_sources (engine);
995 return success;
998 gboolean
999 dconf_engine_change_fast (DConfEngine *engine,
1000 DConfChangeset *changeset,
1001 GError **error)
1003 GList *node;
1005 if (!dconf_engine_changeset_changes_only_writable_keys (engine, changeset, error))
1006 return FALSE;
1008 /* Check for duplicates in the pending queue.
1010 * Note: order doesn't really matter here since "similarity" is an
1011 * equivalence class and we've ensured that there are no pairwise
1012 * similar changes in the queue already (ie: at most we will have only
1013 * one similar item to the one we are adding).
1015 dconf_engine_lock_queues (engine);
1017 for (node = g_queue_peek_head_link (&engine->pending); node; node = node->next)
1019 DConfChangeset *queued_change = node->data;
1021 if (dconf_changeset_is_similar_to (changeset, queued_change))
1023 /* We found a similar item in the queue.
1025 * We want to drop the one that's in the queue already since
1026 * we want our new (more recent) change to take precedence.
1028 * The pending queue owned the changeset, so free it.
1030 g_queue_delete_link (&engine->pending, node);
1031 dconf_changeset_unref (queued_change);
1033 /* There will only have been one, so stop looking. */
1034 break;
1038 /* No matter what we're going to queue up this change, so put it in
1039 * the pending queue now.
1041 * There may be room in the in_flight queue, so we try to manage the
1042 * queue right away in order to try to promote it there (which causes
1043 * the D-Bus message to actually be sent).
1045 * The change might get tossed before being sent if the loop above
1046 * finds it on a future call.
1048 g_queue_push_tail (&engine->pending, dconf_changeset_ref (changeset));
1049 dconf_engine_manage_queue (engine);
1051 dconf_engine_unlock_queues (engine);
1053 /* Emit the signal after dropping the lock to avoid deadlock on re-entry. */
1054 dconf_engine_emit_changes (engine, changeset);
1056 return TRUE;
1059 gboolean
1060 dconf_engine_change_sync (DConfEngine *engine,
1061 DConfChangeset *changeset,
1062 gchar **tag,
1063 GError **error)
1065 GVariant *reply;
1067 if (!dconf_engine_changeset_changes_only_writable_keys (engine, changeset, error))
1068 return FALSE;
1070 /* we know that we have at least one source because we checked writability */
1071 reply = dconf_engine_dbus_call_sync_func (engine->sources[0]->bus_type,
1072 engine->sources[0]->bus_name,
1073 engine->sources[0]->object_path,
1074 "ca.desrt.dconf.Writer", "Change",
1075 dconf_engine_prepare_change (engine, changeset),
1076 G_VARIANT_TYPE ("(s)"), error);
1078 if (reply == NULL)
1079 return FALSE;
1081 /* g_variant_get() is okay with NULL tag */
1082 g_variant_get (reply, "(s)", tag);
1083 g_variant_unref (reply);
1085 return TRUE;
1088 void
1089 dconf_engine_handle_dbus_signal (GBusType type,
1090 const gchar *sender,
1091 const gchar *path,
1092 const gchar *member,
1093 GVariant *body)
1095 if (g_str_equal (member, "Notify"))
1097 const gchar *prefix;
1098 const gchar **changes;
1099 const gchar *tag;
1100 GSList *engines;
1102 if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(sass)")))
1103 return;
1105 g_variant_get (body, "(&s^a&s&s)", &prefix, &changes, &tag);
1107 g_mutex_lock (&dconf_engine_global_lock);
1108 engines = g_slist_copy_deep (dconf_engine_global_list, (GCopyFunc) dconf_engine_ref, NULL);
1109 g_mutex_unlock (&dconf_engine_global_lock);
1111 while (engines)
1113 DConfEngine *engine = engines->data;
1115 /* It's possible that this incoming change notify is for a
1116 * change that we already announced to the client when we
1117 * placed it in the pending queue.
1119 * Check last_handled to determine if we should ignore it.
1121 if (!engine->last_handled || !g_str_equal (engine->last_handled, tag))
1122 dconf_engine_change_notify (engine, prefix, changes, tag, engine->user_data);
1124 engines = g_slist_delete_link (engines, engines);
1126 dconf_engine_unref (engine);
1129 g_free (changes);
1132 else if (g_str_equal (member, "WritabilityNotify"))
1134 if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)")))
1135 return;
1137 g_warning ("Need to handle writability changes"); /* XXX */