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, see <http://www.gnu.org/licenses/>.
17 * Author: Ryan Lortie <desrt@desrt.ca>
22 #define _XOPEN_SOURCE 600
23 #include "dconf-engine.h"
25 #include "../common/dconf-enums.h"
26 #include "../common/dconf-paths.h"
27 #include "../gvdb/gvdb-reader.h"
36 #include "dconf-engine-profile.h"
38 /* The engine has zero or more sources.
40 * If it has zero sources then things are very uninteresting. Nothing
41 * is writable, nothing will ever be written and reads will always
44 * There are two interesting cases when there is a non-zero number of
45 * sources. Writing only ever occurs to the first source, if at all.
46 * Non-first sources are never writable.
48 * The first source may or may not be writable. In the usual case the
49 * first source is the one in the user's home directory and is writable,
50 * but it may be that the profile was setup for read-only access to
51 * system sources only.
53 * In the case that the first source is not writable (and therefore
54 * there are no writable sources), is_writable() will always return
55 * FALSE and no writes will ever be performed.
57 * It's possible to request changes in three ways:
59 * - synchronous: the D-Bus message is immediately sent to the
60 * dconf service and we block until we receive the reply. The change
61 * signal will follow soon thereafter (when we receive the signal on
64 * - asynchronous: typical asynchronous operation: we send the request
65 * and return immediately, notifying using a callback when the
66 * request is completed (and the new value is in the database). The
67 * change signal follows in the same way as with synchronous.
69 * - fast: we record the value locally and signal the change, returning
70 * immediately, as if the value is already in the database (from the
71 * viewpoint of the local process). We keep note of the new value
72 * locally until the service has confirmed that the write was
73 * successful. If the write fails, we emit a change signal. From
74 * the view of the program it looks like the value was successfully
75 * changed but then quickly changed back again by some external
78 * In fast mode we have to do some management of the queue. If we
79 * immediately put all requests "in flight" then we can end up in a
80 * situation where the application writes many values for the same key
81 * and the service is kept (needlessly) busy writing over and over to
82 * the same key for some time after the requests stop coming in.
84 * If we limit the number of in-flight requests and put the other ones
85 * into a pending queue then we can perform merging of similar changes.
86 * If we notice that an item in the pending queue writes to the same
87 * keys as the newly-added request then we can simply drop the existing
88 * request (since its effect will be nullified by the new request).
90 * We want to keep the number of in-flight requests low in order to
91 * maximise our chance of dropping pending items, but we probably want
92 * it higher than 1 so that we can pipeline to hide latency.
94 * In order to minimise complexity, all changes go first to the pending
95 * queue. Changes are dispatched from the pending queue (and moved to
96 * the in-flight queue) when the number of requests in-flight is lower
99 * For both 'in_flight' and 'pending' queues we push to the tail and pop
100 * from the head. This puts the first operation on the head and the
101 * most recent operation on the tail.
103 * Since new operation go first to the pending queue, we find the most
104 * recent operations at the tail of that queue. Since we want to return
105 * the most-recently written value, we therefore scan for values
106 * starting at the tail of the pending queue and ending at the head of
107 * the in-flight queue.
109 * NB: I tell a lie. Async is not supported yet.
111 * Notes about threading:
113 * The engine is oblivious to threads and main contexts.
115 * What this means is that the engine has no interaction with GMainLoop
116 * and will not schedule idles or anything of the sort. All calls made
117 * by the engine to the client library will be made in response to
118 * incoming method calls, from the same thread as the incoming call.
120 * If dconf_engine_call_handle_reply() or
121 * dconf_engine_handle_dbus_signal() are called from 'exotic' threads
122 * (as will often be the case) then the resulting calls to
123 * dconf_engine_change_notify() will come from the same thread. That's
124 * left for the client library to deal with.
126 * All that said, the engine is completely threadsafe. The client
127 * library can call any method from any thread at any time -- as long as
128 * it is willing to deal with receiving the change notifies in those
131 * Thread-safety is implemented using two locks.
133 * The first lock (sources_lock) protects the sources. Although the
134 * sources are only ever read from, it is necessary to lock them because
135 * it is not safe to read during a refresh (when the source is being
136 * closed and reopened). Accordingly, sources_lock need only be
137 * acquired when accessing the parts of the sources that are subject to
138 * change as a result of refreshes; the static parts (like bus type,
139 * object path, etc) can be accessed without holding the lock. The
140 * 'sources' array itself (and 'n_sources') are set at construction and
141 * never change after that.
143 * The second lock (queue_lock) protects the various queues that are
144 * used to implement the "fast" writes described above.
146 * If both locks are held at the same time then the sources lock must
147 * have been acquired first.
150 #define MAX_IN_FLIGHT 2
152 static GSList
*dconf_engine_global_list
;
153 static GMutex dconf_engine_global_lock
;
157 gpointer user_data
; /* Set at construct time */
158 GDestroyNotify free_func
;
161 GMutex sources_lock
; /* This lock is for the sources (ie: refreshing) and state. */
162 guint64 state
; /* Counter that changes every time a source is refreshed. */
163 DConfEngineSource
**sources
; /* Array never changes, but each source changes internally. */
166 GMutex queue_lock
; /* This lock is for pending, in_flight, queue_cond */
167 GCond queue_cond
; /* Signalled when the queues empty */
168 GQueue pending
; /* DConfChangeset */
169 GQueue in_flight
; /* DConfChangeset */
171 gchar
*last_handled
; /* reply tag from last item in in_flight */
174 * establishing and active, are hash tables storing the number
175 * of subscriptions to each path in the two possible states
177 /* active on the client side, but awaiting confirmation from the writer */
178 GHashTable
*establishing
;
179 /* active on the client side, and with a D-Bus match rule established */
183 /* When taking the sources lock we check if any of the databases have
186 * Anything that is accessing the database (even only reading) needs to
187 * be holding the lock (since refreshes could be happening in another
188 * thread), so this makes sense.
190 * We could probably optimise this to avoid checking some databases in
191 * certain cases (ie: we do not need to check the user's database when
192 * we are only interested in checking writability) but this works well
193 * enough for now and is less prone to errors.
195 * We could probably change to a reader/writer situation that is only
196 * holding the write lock when actually making changes during a refresh
197 * but the engine is probably only ever really in use by two threads at
198 * a given time (main thread doing reads, DBus worker thread clearing
199 * the queue) so it seems unlikely that lock contention will become an
202 * If it does, we can revisit this...
205 dconf_engine_acquire_sources (DConfEngine
*engine
)
209 g_mutex_lock (&engine
->sources_lock
);
211 for (i
= 0; i
< engine
->n_sources
; i
++)
212 if (dconf_engine_source_refresh (engine
->sources
[i
]))
217 dconf_engine_release_sources (DConfEngine
*engine
)
219 g_mutex_unlock (&engine
->sources_lock
);
223 dconf_engine_lock_queues (DConfEngine
*engine
)
225 g_mutex_lock (&engine
->queue_lock
);
229 dconf_engine_unlock_queues (DConfEngine
*engine
)
231 g_mutex_unlock (&engine
->queue_lock
);
235 * Adds the count of subscriptions to @path in @from_table to the
236 * corresponding count in @to_table, creating it if it did not exist.
237 * Removes the count from @from_table.
240 dconf_engine_move_subscriptions (GHashTable
*from_counts
,
241 GHashTable
*to_counts
,
244 guint from_count
= GPOINTER_TO_UINT (g_hash_table_lookup (from_counts
, path
));
245 guint old_to_count
= GPOINTER_TO_UINT (g_hash_table_lookup (to_counts
, path
));
247 g_assert (old_to_count
<= G_MAXUINT32
- from_count
);
248 guint new_to_count
= old_to_count
+ from_count
;
251 g_hash_table_remove (from_counts
, path
);
252 g_hash_table_replace (to_counts
,
254 GUINT_TO_POINTER (new_to_count
));
259 * Increments the reference count for the subscription to @path, or sets
260 * it to 1 if it didn’t previously exist.
261 * Returns the new reference count.
264 dconf_engine_inc_subscriptions (GHashTable
*counts
,
267 guint old_count
= GPOINTER_TO_UINT (g_hash_table_lookup (counts
, path
));
269 g_assert (old_count
< G_MAXUINT32
);
270 guint new_count
= old_count
+ 1;
271 g_hash_table_replace (counts
, g_strdup (path
), GUINT_TO_POINTER (new_count
));
276 * Decrements the reference count for the subscription to @path, or
277 * removes it if the new value is 0. The count must exist and be greater
279 * Returns the new reference count, or 0 if it does not exist.
282 dconf_engine_dec_subscriptions (GHashTable
*counts
,
285 guint old_count
= GPOINTER_TO_UINT (g_hash_table_lookup (counts
, path
));
286 g_assert (old_count
> 0);
287 guint new_count
= old_count
- 1;
289 g_hash_table_remove (counts
, path
);
291 g_hash_table_replace (counts
, g_strdup (path
), GUINT_TO_POINTER (new_count
));
296 * Returns the reference count for the subscription to @path, or 0 if it
300 dconf_engine_count_subscriptions (GHashTable
*counts
,
303 return GPOINTER_TO_UINT (g_hash_table_lookup (counts
, path
));
307 dconf_engine_new (const gchar
*profile
,
309 GDestroyNotify free_func
)
313 engine
= g_slice_new0 (DConfEngine
);
314 engine
->user_data
= user_data
;
315 engine
->free_func
= free_func
;
316 engine
->ref_count
= 1;
318 g_mutex_init (&engine
->sources_lock
);
319 g_mutex_init (&engine
->queue_lock
);
320 g_cond_init (&engine
->queue_cond
);
322 engine
->sources
= dconf_engine_profile_open (profile
, &engine
->n_sources
);
324 g_mutex_lock (&dconf_engine_global_lock
);
325 dconf_engine_global_list
= g_slist_prepend (dconf_engine_global_list
, engine
);
326 g_mutex_unlock (&dconf_engine_global_lock
);
328 engine
->establishing
= g_hash_table_new_full (g_str_hash
,
332 engine
->active
= g_hash_table_new_full (g_str_hash
,
341 dconf_engine_unref (DConfEngine
*engine
)
346 ref_count
= engine
->ref_count
;
352 /* We are about to drop the last reference, but there is a chance
353 * that a signal may be happening at this very moment, causing the
354 * engine to gain another reference (due to its position in the
355 * global engine list).
357 * Acquiring the lock here means that either we will remove this
358 * engine from the list first or we will notice the reference
359 * count has increased (and skip the free).
361 g_mutex_lock (&dconf_engine_global_lock
);
362 if (engine
->ref_count
!= 1)
364 g_mutex_unlock (&dconf_engine_global_lock
);
367 dconf_engine_global_list
= g_slist_remove (dconf_engine_global_list
, engine
);
368 g_mutex_unlock (&dconf_engine_global_lock
);
370 g_mutex_clear (&engine
->sources_lock
);
371 g_mutex_clear (&engine
->queue_lock
);
372 g_cond_clear (&engine
->queue_cond
);
374 g_free (engine
->last_handled
);
376 while (!g_queue_is_empty (&engine
->pending
))
377 dconf_changeset_unref ((DConfChangeset
*) g_queue_pop_head (&engine
->pending
));
379 while (!g_queue_is_empty (&engine
->in_flight
))
380 dconf_changeset_unref ((DConfChangeset
*) g_queue_pop_head (&engine
->in_flight
));
382 for (i
= 0; i
< engine
->n_sources
; i
++)
383 dconf_engine_source_free (engine
->sources
[i
]);
385 g_free (engine
->sources
);
387 g_hash_table_unref (engine
->establishing
);
388 g_hash_table_unref (engine
->active
);
390 if (engine
->free_func
)
391 engine
->free_func (engine
->user_data
);
393 g_slice_free (DConfEngine
, engine
);
396 else if (!g_atomic_int_compare_and_exchange (&engine
->ref_count
, ref_count
, ref_count
- 1))
401 dconf_engine_ref (DConfEngine
*engine
)
403 g_atomic_int_inc (&engine
->ref_count
);
409 dconf_engine_get_state (DConfEngine
*engine
)
413 dconf_engine_acquire_sources (engine
);
414 state
= engine
->state
;
415 dconf_engine_release_sources (engine
);
421 dconf_engine_is_writable_internal (DConfEngine
*engine
,
426 /* We must check several things:
428 * - we have at least one source
430 * - the first source is writable
432 * - the key is not locked in a non-writable (ie: non-first) source
434 if (engine
->n_sources
== 0)
437 if (engine
->sources
[0]->writable
== FALSE
)
440 /* Ignore locks in the first source.
442 * Either it is writable and therefore ignoring locks is the right
443 * thing to do, or it's non-writable and we caught that case above.
445 for (i
= 1; i
< engine
->n_sources
; i
++)
446 if (engine
->sources
[i
]->locks
&& gvdb_table_has_value (engine
->sources
[i
]->locks
, key
))
453 dconf_engine_is_writable (DConfEngine
*engine
,
458 dconf_engine_acquire_sources (engine
);
459 writable
= dconf_engine_is_writable_internal (engine
, key
);
460 dconf_engine_release_sources (engine
);
466 dconf_engine_list_locks (DConfEngine
*engine
,
472 if (dconf_is_dir (path
, NULL
))
476 set
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, NULL
);
478 dconf_engine_acquire_sources (engine
);
480 if (engine
->n_sources
> 0 && engine
->sources
[0]->writable
)
484 for (i
= 1; i
< engine
->n_sources
; i
++)
486 if (engine
->sources
[i
]->locks
)
488 strv
= gvdb_table_get_names (engine
->sources
[i
]->locks
, NULL
);
490 for (j
= 0; strv
[j
]; j
++)
492 /* It is not currently possible to lock dirs, so we
493 * don't (yet) have to check the other direction.
495 if (g_str_has_prefix (strv
[j
], path
))
496 g_hash_table_add (set
, strv
[j
]);
506 g_hash_table_add (set
, g_strdup (path
));
508 dconf_engine_release_sources (engine
);
510 strv
= (gchar
**) g_hash_table_get_keys_as_array (set
, (guint
*) length
);
511 g_hash_table_steal_all (set
);
512 g_hash_table_unref (set
);
516 if (dconf_engine_is_writable (engine
, path
))
518 strv
= g_new0 (gchar
*, 0 + 1);
522 strv
= g_new0 (gchar
*, 1 + 1);
523 strv
[0] = g_strdup (path
);
531 dconf_engine_find_key_in_queue (const GQueue
*queue
,
537 /* Tail to head... */
538 for (node
= queue
->tail
; node
; node
= node
->prev
)
539 if (dconf_changeset_get (node
->data
, key
, value
))
546 dconf_engine_read (DConfEngine
*engine
,
547 DConfReadFlags flags
,
548 const GQueue
*read_through
,
551 GVariant
*value
= NULL
;
555 dconf_engine_acquire_sources (engine
);
557 /* There are a number of situations that this function has to deal
558 * with and they interact in unusual ways. We attempt to write the
559 * rules for all cases here:
561 * With respect to the steady-state condition with no locks:
563 * This is the case where there are no changes queued, no
564 * read_through and no locks.
566 * The value returned is the one from the lowest-index source that
567 * contains that value.
569 * With respect to locks:
571 * If a lock is present (except in source #0 where it is ignored)
572 * then we will only return a value found in the source where the
573 * lock was present, or a higher-index source (following the normal
574 * rule that sources with lower indexes take priority).
576 * This statement includes read_through and queued changes. If a
577 * lock is found, we will ignore those.
579 * With respect to flags:
581 * If DCONF_READ_USER_VALUE is given then we completely ignore all
582 * locks, returning the user value all the time, even if it is not
583 * visible (because of a lock). This includes any pending value
584 * that is in the read_through or pending queues.
586 * If DCONF_READ_DEFAULT_VALUE is given then we skip the writable
587 * database and the queues (including read_through, which is
588 * meaningless in this case) and skip directly to the non-writable
589 * databases. This is defined as the value that the user would see
590 * if they were to have just done a reset for that key.
592 * With respect to read_through and queued changed:
594 * We only consider read_through and queued changes in the event
595 * that we have a writable source. This will possibly cause us to
596 * ignore read_through and will have no real effect on the queues
597 * (since they will be empty anyway if we have no writable source).
599 * We only consider read_through and queued changes in the event
600 * that we have not found any locks.
602 * If there is a non-NULL value found in read_through or the queued
603 * changes then we will return that value.
605 * If there is a NULL value (ie: a reset) found in read_through or
606 * the queued changes then we will only ignore any value found in
607 * the first source (which must be writable, or else we would not
608 * have been considering read_through and the queues). This is
609 * consistent with the fact that a reset will unset any value found
610 * in this source but will not affect values found in lower sources.
612 * Put another way: if a non-writable source contains a value for a
613 * particular key then it is impossible for this function to return
616 * We implement the above rules as follows. We have three state
617 * tracking variables:
619 * - lock_level: records if and where we found a lock
621 * - found_key: records if we found the key in any queue
623 * - value: records the value of the found key (NULL for resets)
625 * We take these steps:
627 * 1. check for lockdown. If we find a lock then we prevent any
628 * other sources (including read_through and pending/in-flight)
629 * from affecting the value of the key.
631 * We record the result of this in the lock_level variable. Zero
632 * means that no locks were found. Non-zero means that a lock was
633 * found in the source with the index given by the variable.
635 * 2. check the uncommitted changes in the read_through list as the
636 * highest priority. This is only done if we have a writable
637 * source and no locks were found.
639 * If we found an entry in the read_through then we set
640 * 'found_key' to TRUE and set 'value' to the value that we found
641 * (which will be NULL in the case of finding a reset request).
643 * 3. check our pending and in-flight "fast" changes (in that order).
644 * This is only done if we have a writable source and no locks
645 * were found. It is also only done if we did not find the key in
648 * 4. check the first source, if there is one.
650 * This is only done if 'found_key' is FALSE. If 'found_key' is
651 * TRUE then it means that the first database was writable and we
652 * either found a value that will replace it (value != NULL) or
653 * found a pending reset (value == NULL) that will unset it.
655 * We only actually do this step if we have a writable first
656 * source and no locks found, otherwise we just let step 5 do all
659 * 5. check the remaining sources.
661 * We do this until we have value != NULL. Even if found_key was
662 * TRUE, the reset that was requested will not have affected the
663 * lower-level databases.
666 /* Step 1. Check for locks.
668 * Note: i > 0 (strictly). Ignore locks for source #0.
670 if (~flags
& DCONF_READ_USER_VALUE
)
671 for (i
= engine
->n_sources
- 1; i
> 0; i
--)
672 if (engine
->sources
[i
]->locks
&& gvdb_table_has_value (engine
->sources
[i
]->locks
, key
))
678 /* Only do steps 2 to 4 if we have no locks and we have a writable source. */
679 if (!lock_level
&& engine
->n_sources
!= 0 && engine
->sources
[0]->writable
)
681 gboolean found_key
= FALSE
;
683 /* If the user has requested the default value only, then ensure
684 * that we "find" a NULL value here. This is equivalent to the
685 * user having reset the key, which is the definition of this
688 if (flags
& DCONF_READ_DEFAULT_VALUE
)
691 /* Step 2. Check read_through. */
692 if (!found_key
&& read_through
)
693 found_key
= dconf_engine_find_key_in_queue (read_through
, key
, &value
);
695 /* Step 3. Check queued changes if we didn't find it in read_through.
697 * NB: We may want to optimise this to avoid taking the lock in
698 * the case that we know both queues are empty.
702 dconf_engine_lock_queues (engine
);
704 /* Check the pending queue first because those were submitted
707 found_key
= dconf_engine_find_key_in_queue (&engine
->pending
, key
, &value
) ||
708 dconf_engine_find_key_in_queue (&engine
->in_flight
, key
, &value
);
710 dconf_engine_unlock_queues (engine
);
713 /* Step 4. Check the first source. */
714 if (!found_key
&& engine
->sources
[0]->values
)
715 value
= gvdb_table_get_value (engine
->sources
[0]->values
, key
);
717 /* We already checked source #0 (or ignored it, as appropriate).
719 * Abuse the lock_level variable to get step 5 to skip this one.
724 /* Step 5. Check the remaining sources, until value != NULL. */
725 if (~flags
& DCONF_READ_USER_VALUE
)
726 for (i
= lock_level
; value
== NULL
&& i
< engine
->n_sources
; i
++)
728 if (engine
->sources
[i
]->values
== NULL
)
731 if ((value
= gvdb_table_get_value (engine
->sources
[i
]->values
, key
)))
735 dconf_engine_release_sources (engine
);
741 dconf_engine_list (DConfEngine
*engine
,
752 /* This function is unreliable in the presence of pending changes.
755 * Consider the case that we list("/a/") and a pending request has a
756 * reset request recorded for "/a/b/c". The question of if "b/"
757 * should appear in the output rests on if "/a/b/d" also exists.
759 * Put another way: If "/a/b/c" is the only key in "/a/b/" then
760 * resetting it would mean that "/a/b/" stops existing (and we should
761 * not include it in the output). If there are others keys then it
762 * will continue to exist and we should include it.
764 * Instead of trying to sort this out, we just ignore the pending
765 * requests and report what the on-disk file says.
768 results
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, NULL
);
770 dconf_engine_acquire_sources (engine
);
772 for (i
= 0; i
< engine
->n_sources
; i
++)
774 gchar
**partial_list
;
777 if (engine
->sources
[i
]->values
== NULL
)
780 partial_list
= gvdb_table_list (engine
->sources
[i
]->values
, dir
);
782 if (partial_list
!= NULL
)
784 for (j
= 0; partial_list
[j
]; j
++)
785 /* Steal the keys from the list. */
786 g_hash_table_add (results
, partial_list
[j
]);
788 /* Free only the list. */
789 g_free (partial_list
);
793 dconf_engine_release_sources (engine
);
795 n_items
= g_hash_table_size (results
);
796 list
= g_new (gchar
*, n_items
+ 1);
799 g_hash_table_iter_init (&iter
, results
);
800 while (g_hash_table_iter_next (&iter
, &key
, NULL
))
802 g_hash_table_iter_steal (&iter
);
806 g_assert_cmpint (i
, ==, n_items
);
811 g_hash_table_unref (results
);
816 typedef void (* DConfEngineCallHandleCallback
) (DConfEngine
*engine
,
819 const GError
*error
);
821 struct _DConfEngineCallHandle
824 DConfEngineCallHandleCallback callback
;
825 const GVariantType
*expected_reply
;
829 dconf_engine_call_handle_new (DConfEngine
*engine
,
830 DConfEngineCallHandleCallback callback
,
831 const GVariantType
*expected_reply
,
834 DConfEngineCallHandle
*handle
;
836 g_assert (engine
!= NULL
);
837 g_assert (callback
!= NULL
);
838 g_assert (size
>= sizeof (DConfEngineCallHandle
));
840 handle
= g_malloc0 (size
);
841 handle
->engine
= dconf_engine_ref (engine
);
842 handle
->callback
= callback
;
843 handle
->expected_reply
= expected_reply
;
849 dconf_engine_call_handle_get_expected_type (DConfEngineCallHandle
*handle
)
852 return handle
->expected_reply
;
858 dconf_engine_call_handle_reply (DConfEngineCallHandle
*handle
,
865 (* handle
->callback
) (handle
->engine
, handle
, parameter
, error
);
869 dconf_engine_call_handle_free (DConfEngineCallHandle
*handle
)
871 dconf_engine_unref (handle
->engine
);
875 /* returns floating */
877 dconf_engine_make_match_rule (DConfEngineSource
*source
,
883 rule
= g_strdup_printf ("type='signal',"
884 "interface='ca.desrt.dconf.Writer',"
890 params
= g_variant_new ("(s)", rule
);
899 DConfEngineCallHandle handle
;
907 dconf_engine_watch_established (DConfEngine
*engine
,
912 OutstandingWatch
*ow
= handle
;
917 /* more on the way... */
920 if (ow
->state
!= dconf_engine_get_state (engine
))
922 const gchar
* const changes
[] = { "", NULL
};
924 /* Our recorded state does not match the current state. Something
925 * must have changed while our watch requests were on the wire.
927 * We don't know what changed, so we can just say that potentially
928 * everything under the path being watched changed. This case is
929 * very rare, anyway...
931 dconf_engine_change_notify (engine
, ow
->path
, changes
, NULL
, FALSE
, NULL
, engine
->user_data
);
934 guint num_establishing
= dconf_engine_count_subscriptions (engine
->establishing
,
936 if (num_establishing
> 0)
937 // Subscription(s): establishing -> active
938 dconf_engine_move_subscriptions (engine
->establishing
,
942 dconf_engine_call_handle_free (handle
);
946 dconf_engine_watch_fast (DConfEngine
*engine
,
949 guint num_establishing
= dconf_engine_count_subscriptions (engine
->establishing
, path
);
950 guint num_active
= dconf_engine_count_subscriptions (engine
->active
, path
);
952 // Subscription: inactive -> active
953 dconf_engine_inc_subscriptions (engine
->active
, path
);
955 // Subscription: inactive -> establishing
956 num_establishing
= dconf_engine_inc_subscriptions (engine
->establishing
,
958 if (num_establishing
> 1 || num_active
> 0)
961 OutstandingWatch
*ow
;
964 if (engine
->n_sources
== 0)
967 /* It's possible (although rare) that the dconf database could change
968 * while our match rule is on the wire.
970 * Since we returned immediately (suggesting to the user that the
971 * watch was already established) we could have a race.
973 * To deal with this, we use the current state counter to ensure that nothing
974 * changes while the watch requests are on the wire.
976 ow
= dconf_engine_call_handle_new (engine
, dconf_engine_watch_established
,
977 G_VARIANT_TYPE_UNIT
, sizeof (OutstandingWatch
));
978 ow
->state
= dconf_engine_get_state (engine
);
979 ow
->path
= g_strdup (path
);
981 /* We start getting async calls returned as soon as we start dispatching them,
982 * so we must not touch the 'ow' struct after we send the first one.
984 for (i
= 0; i
< engine
->n_sources
; i
++)
985 if (engine
->sources
[i
]->bus_type
)
988 for (i
= 0; i
< engine
->n_sources
; i
++)
989 if (engine
->sources
[i
]->bus_type
)
990 dconf_engine_dbus_call_async_func (engine
->sources
[i
]->bus_type
, "org.freedesktop.DBus",
991 "/org/freedesktop/DBus", "org.freedesktop.DBus", "AddMatch",
992 dconf_engine_make_match_rule (engine
->sources
[i
], path
),
997 dconf_engine_unwatch_fast (DConfEngine
*engine
,
1000 guint num_active
= dconf_engine_count_subscriptions (engine
->active
, path
);
1001 guint num_establishing
= dconf_engine_count_subscriptions (engine
->establishing
, path
);
1004 // Client code cannot unsubscribe if it is not subscribed
1005 g_assert (num_active
> 0 || num_establishing
> 0);
1006 if (num_active
== 0)
1007 // Subscription: establishing -> inactive
1008 num_establishing
= dconf_engine_dec_subscriptions (engine
->establishing
, path
);
1010 // Subscription: active -> inactive
1011 num_active
= dconf_engine_dec_subscriptions (engine
->active
, path
);
1013 if (num_active
> 0 || num_establishing
> 0)
1016 for (i
= 0; i
< engine
->n_sources
; i
++)
1017 if (engine
->sources
[i
]->bus_type
)
1018 dconf_engine_dbus_call_async_func (engine
->sources
[i
]->bus_type
, "org.freedesktop.DBus",
1019 "/org/freedesktop/DBus", "org.freedesktop.DBus", "RemoveMatch",
1020 dconf_engine_make_match_rule (engine
->sources
[i
], path
), NULL
, NULL
);
1024 dconf_engine_handle_match_rule_sync (DConfEngine
*engine
,
1025 const gchar
*method_name
,
1030 /* We need not hold any locks here because we are only touching static
1031 * things: the number of sources, and static properties of each source
1034 * This function silently ignores all errors.
1037 for (i
= 0; i
< engine
->n_sources
; i
++)
1041 if (!engine
->sources
[i
]->bus_type
)
1044 result
= dconf_engine_dbus_call_sync_func (engine
->sources
[i
]->bus_type
, "org.freedesktop.DBus",
1045 "/org/freedesktop/DBus", "org.freedesktop.DBus", method_name
,
1046 dconf_engine_make_match_rule (engine
->sources
[i
], path
),
1047 G_VARIANT_TYPE_UNIT
, NULL
);
1050 g_variant_unref (result
);
1055 dconf_engine_watch_sync (DConfEngine
*engine
,
1058 guint num_active
= dconf_engine_inc_subscriptions (engine
->active
, path
);
1059 if (num_active
== 1)
1060 dconf_engine_handle_match_rule_sync (engine
, "AddMatch", path
);
1064 dconf_engine_unwatch_sync (DConfEngine
*engine
,
1067 guint num_active
= dconf_engine_dec_subscriptions (engine
->active
, path
);
1068 if (num_active
== 0)
1069 dconf_engine_handle_match_rule_sync (engine
, "RemoveMatch", path
);
1074 DConfEngineCallHandle handle
;
1076 DConfChangeset
*change
;
1077 } OutstandingChange
;
1080 dconf_engine_prepare_change (DConfEngine
*engine
,
1081 DConfChangeset
*change
)
1083 GVariant
*serialised
;
1085 serialised
= dconf_changeset_serialise (change
);
1087 return g_variant_new_from_data (G_VARIANT_TYPE ("(ay)"),
1088 g_variant_get_data (serialised
), g_variant_get_size (serialised
), TRUE
,
1089 (GDestroyNotify
) g_variant_unref
, g_variant_ref_sink (serialised
));
1092 /* This function promotes changes from the pending queue to the
1093 * in-flight queue by sending the appropriate D-Bus message.
1095 * Of course, this is only possible when there are pending items and
1096 * room in the in-flight queue. For this reason, this function gets
1097 * called in two situations:
1099 * - an item has been added to the pending queue (due to an API call)
1101 * - an item has been removed from the inflight queue (due to a D-Bus
1102 * reply having been received)
1104 * It will move a maximum of one item.
1106 static void dconf_engine_manage_queue (DConfEngine
*engine
);
1109 dconf_engine_emit_changes (DConfEngine
*engine
,
1110 DConfChangeset
*changeset
,
1111 gpointer origin_tag
)
1113 const gchar
*prefix
;
1114 const gchar
* const *changes
;
1116 if (dconf_changeset_describe (changeset
, &prefix
, &changes
, NULL
))
1117 dconf_engine_change_notify (engine
, prefix
, changes
, NULL
, FALSE
, origin_tag
, engine
->user_data
);
1121 dconf_engine_change_completed (DConfEngine
*engine
,
1124 const GError
*error
)
1126 OutstandingChange
*oc
= handle
;
1128 dconf_engine_lock_queues (engine
);
1130 /* D-Bus guarantees ordered delivery of messages.
1132 * The dconf-service handles requests in-order.
1134 * The reply we just received should therefore be at the head of
1135 * our 'in flight' queue.
1137 * Due to https://bugs.freedesktop.org/show_bug.cgi?id=59780 it is
1138 * possible that we receive an out-of-sequence error message, however,
1139 * so only assume that messages are in-order for positive replies.
1143 DConfChangeset
*expected
;
1145 expected
= g_queue_pop_head (&engine
->in_flight
);
1146 g_assert (expected
&& oc
->change
== expected
);
1152 g_assert (error
!= NULL
);
1154 found
= g_queue_remove (&engine
->in_flight
, oc
->change
);
1158 /* We just popped a change from the in-flight queue, possibly
1159 * making room for another to be added. Check that.
1161 dconf_engine_manage_queue (engine
);
1162 dconf_engine_unlock_queues (engine
);
1164 /* Deal with the reply we got. */
1167 /* The write worked.
1169 * We already sent a change notification for this item when we
1170 * added it to the pending queue and we don't want to send another
1171 * one again. At the same time, it's very likely that we're just
1172 * about to receive a change signal from the service.
1174 * The tag sent as part of the reply to the Change call will be
1175 * the same tag as on the change notification signal. Record that
1176 * tag so that we can ignore the signal when it comes.
1178 * last_handled is only ever touched from the worker thread
1180 g_free (engine
->last_handled
);
1181 g_variant_get (reply
, "(s)", &engine
->last_handled
);
1186 /* Some kind of unexpected failure occurred while attempting to
1187 * commit the change.
1189 * There's not much we can do here except to drop our local copy
1190 * of the change (and notify that it is gone) and print the error
1191 * message as a warning.
1193 g_warning ("failed to commit changes to dconf: %s", error
->message
);
1194 dconf_engine_emit_changes (engine
, oc
->change
, NULL
);
1197 dconf_changeset_unref (oc
->change
);
1198 dconf_engine_call_handle_free (handle
);
1202 dconf_engine_manage_queue (DConfEngine
*engine
)
1204 if (!g_queue_is_empty (&engine
->pending
) && g_queue_get_length (&engine
->in_flight
) < MAX_IN_FLIGHT
)
1206 OutstandingChange
*oc
;
1207 GVariant
*parameters
;
1209 oc
= dconf_engine_call_handle_new (engine
, dconf_engine_change_completed
,
1210 G_VARIANT_TYPE ("(s)"), sizeof (OutstandingChange
));
1212 oc
->change
= g_queue_pop_head (&engine
->pending
);
1214 parameters
= dconf_engine_prepare_change (engine
, oc
->change
);
1216 dconf_engine_dbus_call_async_func (engine
->sources
[0]->bus_type
,
1217 engine
->sources
[0]->bus_name
,
1218 engine
->sources
[0]->object_path
,
1219 "ca.desrt.dconf.Writer", "Change",
1220 parameters
, &oc
->handle
, NULL
);
1222 g_queue_push_tail (&engine
->in_flight
, oc
->change
);
1225 if (g_queue_is_empty (&engine
->in_flight
))
1227 /* The in-flight queue should not be empty if we have changes
1230 g_assert (g_queue_is_empty (&engine
->pending
));
1232 g_cond_broadcast (&engine
->queue_cond
);
1237 dconf_engine_is_writable_changeset_predicate (const gchar
*key
,
1241 DConfEngine
*engine
= user_data
;
1243 /* Resets absolutely always succeed -- even in the case that there is
1244 * not even a writable database.
1246 return value
== NULL
|| dconf_engine_is_writable_internal (engine
, key
);
1250 dconf_engine_changeset_changes_only_writable_keys (DConfEngine
*engine
,
1251 DConfChangeset
*changeset
,
1254 gboolean success
= TRUE
;
1256 dconf_engine_acquire_sources (engine
);
1258 if (!dconf_changeset_all (changeset
, dconf_engine_is_writable_changeset_predicate
, engine
))
1260 g_set_error_literal (error
, DCONF_ERROR
, DCONF_ERROR_NOT_WRITABLE
,
1261 "The operation attempted to modify one or more non-writable keys");
1265 dconf_engine_release_sources (engine
);
1271 dconf_engine_change_fast (DConfEngine
*engine
,
1272 DConfChangeset
*changeset
,
1273 gpointer origin_tag
,
1278 if (dconf_changeset_is_empty (changeset
))
1281 if (!dconf_engine_changeset_changes_only_writable_keys (engine
, changeset
, error
))
1284 dconf_changeset_seal (changeset
);
1286 /* Check for duplicates in the pending queue.
1288 * Note: order doesn't really matter here since "similarity" is an
1289 * equivalence class and we've ensured that there are no pairwise
1290 * similar changes in the queue already (ie: at most we will have only
1291 * one similar item to the one we are adding).
1293 dconf_engine_lock_queues (engine
);
1295 for (node
= g_queue_peek_head_link (&engine
->pending
); node
; node
= node
->next
)
1297 DConfChangeset
*queued_change
= node
->data
;
1299 if (dconf_changeset_is_similar_to (changeset
, queued_change
))
1301 /* We found a similar item in the queue.
1303 * We want to drop the one that's in the queue already since
1304 * we want our new (more recent) change to take precedence.
1306 * The pending queue owned the changeset, so free it.
1308 g_queue_delete_link (&engine
->pending
, node
);
1309 dconf_changeset_unref (queued_change
);
1311 /* There will only have been one, so stop looking. */
1316 /* No matter what we're going to queue up this change, so put it in
1317 * the pending queue now.
1319 * There may be room in the in_flight queue, so we try to manage the
1320 * queue right away in order to try to promote it there (which causes
1321 * the D-Bus message to actually be sent).
1323 * The change might get tossed before being sent if the loop above
1324 * finds it on a future call.
1326 g_queue_push_tail (&engine
->pending
, dconf_changeset_ref (changeset
));
1327 dconf_engine_manage_queue (engine
);
1329 dconf_engine_unlock_queues (engine
);
1331 /* Emit the signal after dropping the lock to avoid deadlock on re-entry. */
1332 dconf_engine_emit_changes (engine
, changeset
, origin_tag
);
1338 dconf_engine_change_sync (DConfEngine
*engine
,
1339 DConfChangeset
*changeset
,
1345 if (dconf_changeset_is_empty (changeset
))
1348 *tag
= g_strdup ("");
1353 if (!dconf_engine_changeset_changes_only_writable_keys (engine
, changeset
, error
))
1356 dconf_changeset_seal (changeset
);
1358 /* we know that we have at least one source because we checked writability */
1359 reply
= dconf_engine_dbus_call_sync_func (engine
->sources
[0]->bus_type
,
1360 engine
->sources
[0]->bus_name
,
1361 engine
->sources
[0]->object_path
,
1362 "ca.desrt.dconf.Writer", "Change",
1363 dconf_engine_prepare_change (engine
, changeset
),
1364 G_VARIANT_TYPE ("(s)"), error
);
1369 /* g_variant_get() is okay with NULL tag */
1370 g_variant_get (reply
, "(s)", tag
);
1371 g_variant_unref (reply
);
1377 dconf_engine_is_interested_in_signal (DConfEngine
*engine
,
1379 const gchar
*sender
,
1384 for (i
= 0; i
< engine
->n_sources
; i
++)
1386 DConfEngineSource
*source
= engine
->sources
[i
];
1388 if (source
->bus_type
== bus_type
&& g_str_equal (source
->object_path
, path
))
1396 dconf_engine_handle_dbus_signal (GBusType type
,
1397 const gchar
*sender
,
1398 const gchar
*object_path
,
1399 const gchar
*member
,
1402 if (g_str_equal (member
, "Notify"))
1404 const gchar
*prefix
;
1405 const gchar
**changes
;
1409 if (!g_variant_is_of_type (body
, G_VARIANT_TYPE ("(sass)")))
1412 g_variant_get (body
, "(&s^a&s&s)", &prefix
, &changes
, &tag
);
1415 if (changes
[0] == NULL
)
1416 /* No changes? Do nothing. */
1419 if (dconf_is_key (prefix
, NULL
))
1421 /* If the prefix is a key then the changes must be ['']. */
1422 if (changes
[0][0] || changes
[1])
1425 else if (dconf_is_dir (prefix
, NULL
))
1427 /* If the prefix is a dir then we can have changes within that
1428 * dir, but they must be rel paths.
1432 * ('/a/', ['b', 'c/']) == ['/a/b', '/a/c/']
1436 for (i
= 0; changes
[i
]; i
++)
1437 if (!dconf_is_rel_path (changes
[i
], NULL
))
1441 /* Not a key or a dir? */
1444 g_mutex_lock (&dconf_engine_global_lock
);
1445 engines
= g_slist_copy_deep (dconf_engine_global_list
, (GCopyFunc
) dconf_engine_ref
, NULL
);
1446 g_mutex_unlock (&dconf_engine_global_lock
);
1450 DConfEngine
*engine
= engines
->data
;
1452 /* It's possible that this incoming change notify is for a
1453 * change that we already announced to the client when we
1454 * placed it in the pending queue.
1456 * Check last_handled to determine if we should ignore it.
1458 if (!engine
->last_handled
|| !g_str_equal (engine
->last_handled
, tag
))
1459 if (dconf_engine_is_interested_in_signal (engine
, type
, sender
, object_path
))
1460 dconf_engine_change_notify (engine
, prefix
, changes
, tag
, FALSE
, NULL
, engine
->user_data
);
1462 engines
= g_slist_delete_link (engines
, engines
);
1464 dconf_engine_unref (engine
);
1471 else if (g_str_equal (member
, "WritabilityNotify"))
1473 const gchar
*empty_str_list
[] = { "", NULL
};
1477 if (!g_variant_is_of_type (body
, G_VARIANT_TYPE ("(s)")))
1480 g_variant_get (body
, "(&s)", &path
);
1482 /* Rejecting junk here is relatively straightforward */
1483 if (!dconf_is_path (path
, NULL
))
1486 g_mutex_lock (&dconf_engine_global_lock
);
1487 engines
= g_slist_copy_deep (dconf_engine_global_list
, (GCopyFunc
) dconf_engine_ref
, NULL
);
1488 g_mutex_unlock (&dconf_engine_global_lock
);
1492 DConfEngine
*engine
= engines
->data
;
1494 if (dconf_engine_is_interested_in_signal (engine
, type
, sender
, object_path
))
1495 dconf_engine_change_notify (engine
, path
, empty_str_list
, "", TRUE
, NULL
, engine
->user_data
);
1497 engines
= g_slist_delete_link (engines
, engines
);
1499 dconf_engine_unref (engine
);
1505 dconf_engine_has_outstanding (DConfEngine
*engine
)
1509 /* The in-flight queue will never be empty unless the pending queue is
1510 * also empty, so we only really need to check one of them...
1512 dconf_engine_lock_queues (engine
);
1513 has
= !g_queue_is_empty (&engine
->in_flight
);
1514 dconf_engine_unlock_queues (engine
);
1520 dconf_engine_sync (DConfEngine
*engine
)
1522 dconf_engine_lock_queues (engine
);
1523 while (!g_queue_is_empty (&engine
->in_flight
))
1524 g_cond_wait (&engine
->queue_cond
, &engine
->queue_lock
);
1525 dconf_engine_unlock_queues (engine
);