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>
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
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
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
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
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
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
;
155 gpointer user_data
; /* Set at construct time */
156 GDestroyNotify free_func
;
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. */
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
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
190 * If it does, we can revisit this...
193 dconf_engine_acquire_sources (DConfEngine
*engine
)
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
]))
205 dconf_engine_release_sources (DConfEngine
*engine
)
207 g_mutex_unlock (&engine
->sources_lock
);
211 dconf_engine_lock_queues (DConfEngine
*engine
)
213 g_mutex_lock (&engine
->queue_lock
);
217 dconf_engine_unlock_queues (DConfEngine
*engine
)
219 g_mutex_unlock (&engine
->queue_lock
);
223 dconf_engine_new (gpointer user_data
,
224 GDestroyNotify free_func
)
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
);
251 dconf_engine_unref (DConfEngine
*engine
)
253 if (g_atomic_int_dec_and_test (&engine
->ref_count
))
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
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()
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
);
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
);
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
);
315 dconf_engine_ref (DConfEngine
*engine
)
317 g_atomic_int_inc (&engine
->ref_count
);
323 dconf_engine_get_state (DConfEngine
*engine
)
327 dconf_engine_acquire_sources (engine
);
328 state
= engine
->state
;
329 dconf_engine_release_sources (engine
);
335 dconf_engine_is_writable_internal (DConfEngine
*engine
,
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)
351 if (engine
->sources
[0]->writable
== 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
))
367 dconf_engine_is_writable (DConfEngine
*engine
,
372 dconf_engine_acquire_sources (engine
);
373 writable
= dconf_engine_is_writable_internal (engine
, key
);
374 dconf_engine_release_sources (engine
);
380 dconf_engine_find_key_in_queue (GQueue
*queue
,
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
))
395 dconf_engine_read (DConfEngine
*engine
,
396 DConfChangesetList
*read_through
,
399 GVariant
*value
= NULL
;
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
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
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
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
))
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. */
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.
528 dconf_engine_lock_queues (engine
);
530 /* Check the pending queue first because those were submitted
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.
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
)
556 if ((value
= gvdb_table_get_value (engine
->sources
[i
]->values
, key
)))
560 dconf_engine_release_sources (engine
);
566 dconf_engine_list (DConfEngine
*engine
,
577 /* This function is unreliable in the presence of pending changes.
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
;
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);
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
);
628 g_assert_cmpint (i
, ==, n_items
);
633 g_hash_table_unref (results
);
638 typedef void (* DConfEngineCallHandleCallback
) (DConfEngine
*engine
,
641 const GError
*error
);
643 struct _DConfEngineCallHandle
646 DConfEngineCallHandleCallback callback
;
647 const GVariantType
*expected_reply
;
651 dconf_engine_call_handle_new (DConfEngine
*engine
,
652 DConfEngineCallHandleCallback callback
,
653 const GVariantType
*expected_reply
,
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
;
671 dconf_engine_call_handle_reply (DConfEngineCallHandle
*handle
,
678 (* handle
->callback
) (handle
->engine
, handle
, parameter
, error
);
682 dconf_engine_call_handle_free (DConfEngineCallHandle
*handle
)
684 dconf_engine_unref (handle
->engine
);
688 /* returns floating */
690 dconf_engine_make_match_rule (DConfEngineSource
*source
,
696 rule
= g_strdup_printf ("type='signal',"
697 "interface='ca.desrt.dconf.Writer',"
703 params
= g_variant_new ("(s)", rule
);
712 DConfEngineCallHandle handle
;
719 dconf_engine_watch_established (DConfEngine
*engine
,
724 OutstandingWatch
*ow
= handle
;
729 /* more on the way... */
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
);
747 dconf_engine_watch_fast (DConfEngine
*engine
,
750 OutstandingWatch
*ow
;
753 if (engine
->n_sources
== 0)
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
),
778 dconf_engine_unwatch_fast (DConfEngine
*engine
,
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
);
790 dconf_engine_handle_match_rule_sync (DConfEngine
*engine
,
791 const gchar
*method_name
,
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
800 * This function silently ignores all errors.
803 for (i
= 0; i
< engine
->n_sources
; i
++)
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
);
813 g_variant_unref (result
);
818 dconf_engine_watch_sync (DConfEngine
*engine
,
821 dconf_engine_handle_match_rule_sync (engine
, "AddMatch", path
);
825 dconf_engine_unwatch_sync (DConfEngine
*engine
,
828 dconf_engine_handle_match_rule_sync (engine
, "RemoveMatch", path
);
833 DConfEngineCallHandle handle
;
835 DConfChangeset
*change
;
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
);
868 dconf_engine_emit_changes (DConfEngine
*engine
,
869 DConfChangeset
*changeset
)
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
);
879 dconf_engine_change_completed (DConfEngine
*engine
,
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. */
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
);
927 /* Some kind of unexpected failure occured while attempting to
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
);
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
);
968 dconf_engine_is_writable_changeset_predicate (const gchar
*key
,
972 DConfEngine
*engine
= user_data
;
974 return dconf_engine_is_writable_internal (engine
, key
);
978 dconf_engine_changeset_changes_only_writable_keys (DConfEngine
*engine
,
979 DConfChangeset
*changeset
,
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");
993 dconf_engine_release_sources (engine
);
999 dconf_engine_change_fast (DConfEngine
*engine
,
1000 DConfChangeset
*changeset
,
1005 if (!dconf_engine_changeset_changes_only_writable_keys (engine
, changeset
, error
))
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. */
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
);
1060 dconf_engine_change_sync (DConfEngine
*engine
,
1061 DConfChangeset
*changeset
,
1067 if (!dconf_engine_changeset_changes_only_writable_keys (engine
, changeset
, error
))
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
);
1081 /* g_variant_get() is okay with NULL tag */
1082 g_variant_get (reply
, "(s)", tag
);
1083 g_variant_unref (reply
);
1089 dconf_engine_handle_dbus_signal (GBusType type
,
1090 const gchar
*sender
,
1092 const gchar
*member
,
1095 if (g_str_equal (member
, "Notify"))
1097 const gchar
*prefix
;
1098 const gchar
**changes
;
1102 if (!g_variant_is_of_type (body
, G_VARIANT_TYPE ("(sass)")))
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
);
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
);
1132 else if (g_str_equal (member
, "WritabilityNotify"))
1134 if (!g_variant_is_of_type (body
, G_VARIANT_TYPE ("(s)")))
1137 g_warning ("Need to handle writability changes"); /* XXX */