GSettingsBackend: make signal dispatch threadsafe
authorRyan Lortie <desrt@desrt.ca>
Sun, 16 May 2010 18:17:34 +0000 (14:17 -0400)
committerRyan Lortie <desrt@desrt.ca>
Mon, 17 May 2010 11:16:37 +0000 (07:16 -0400)
This commit fixes up a few race conditions in the GSettingsBackend, mostly with
respect to change notifications occuring at the same time as the last reference
count on a GSettings is dropped.  With GDBus feeding us our incoming signals in
a separate thread, this is something that could easily happen.

gio/gdelayedsettingsbackend.c
gio/gsettings.c
gio/gsettingsbackend.c
gio/gsettingsbackendinternal.h

index ed18058..8eeaa6a 100644 (file)
@@ -199,11 +199,11 @@ g_delayed_settings_backend_revert (GDelayedSettingsBackend *delayed)
 /* change notification */
 static void
 delayed_backend_changed (GSettingsBackend *backend,
+                         GObject          *target,
                          const gchar      *key,
-                         gpointer          origin_tag,
-                         gpointer          user_data)
+                         gpointer          origin_tag)
 {
-  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
+  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
 
   if (origin_tag != delayed->priv)
     g_settings_backend_changed (G_SETTINGS_BACKEND (delayed),
@@ -212,12 +212,12 @@ delayed_backend_changed (GSettingsBackend *backend,
 
 static void
 delayed_backend_keys_changed (GSettingsBackend    *backend,
+                              GObject             *target,
                               const gchar         *path,
                               const gchar * const *items,
-                              gpointer             origin_tag,
-                              gpointer             user_data)
+                              gpointer             origin_tag)
 {
-  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
+  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
 
   if (origin_tag != delayed->priv)
     g_settings_backend_keys_changed (G_SETTINGS_BACKEND (delayed),
@@ -225,12 +225,12 @@ delayed_backend_keys_changed (GSettingsBackend    *backend,
 }
 
 static void
-delayed_backend_path_changed (GSettingsBackend    *backend,
-                              const gchar         *path,
-                              gpointer             origin_tag,
-                              gpointer             user_data)
+delayed_backend_path_changed (GSettingsBackend *backend,
+                              GObject          *target,
+                              const gchar      *path,
+                              gpointer          origin_tag)
 {
-  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
+  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
 
   if (origin_tag != delayed->priv)
     g_settings_backend_path_changed (G_SETTINGS_BACKEND (delayed),
@@ -239,10 +239,10 @@ delayed_backend_path_changed (GSettingsBackend    *backend,
 
 static void
 delayed_backend_writable_changed (GSettingsBackend *backend,
-                                  const gchar      *key,
-                                  gpointer          user_data)
+                                  GObject          *target,
+                                  const gchar      *key)
 {
-  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
+  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
 
   if (g_tree_lookup (delayed->priv->delayed, key) &&
       !g_settings_backend_get_writable (delayed->priv->backend, key))
@@ -285,10 +285,10 @@ check_prefix (gpointer key,
 
 static void
 delayed_backend_path_writable_changed (GSettingsBackend *backend,
-                                       const gchar      *path,
-                                       gpointer          user_data)
+                                       GObject          *target,
+                                       const gchar      *path)
 {
-  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
+  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
   gsize n_keys;
 
   n_keys = g_tree_nnodes (delayed->priv->delayed);
@@ -323,7 +323,6 @@ g_delayed_settings_backend_finalize (GObject *object)
 {
   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (object);
 
-  g_settings_backend_unwatch (delayed->priv->backend, delayed);
   g_object_unref (delayed->priv->backend);
 }
 
@@ -367,13 +366,12 @@ g_delayed_settings_backend_new (GSettingsBackend *backend,
   delayed->priv->backend = g_object_ref (backend);
   delayed->priv->owner = owner;
 
-  g_settings_backend_watch (delayed->priv->backend, NULL,
+  g_settings_backend_watch (delayed->priv->backend, G_OBJECT (delayed), NULL,
                             delayed_backend_changed,
                             delayed_backend_path_changed,
                             delayed_backend_keys_changed,
                             delayed_backend_writable_changed,
-                            delayed_backend_path_writable_changed,
-                            delayed);
+                            delayed_backend_path_writable_changed);
 
   return delayed;
 }
index aa465ea..f53eac7 100644 (file)
@@ -224,11 +224,11 @@ g_settings_real_writable_change_event (GSettings *settings,
 
 static void
 settings_backend_changed (GSettingsBackend    *backend,
+                          GObject             *target,
                           const gchar         *key,
-                          gpointer             origin_tag,
-                          gpointer             user_data)
+                          gpointer             origin_tag)
 {
-  GSettings *settings = G_SETTINGS (user_data);
+  GSettings *settings = G_SETTINGS (target);
   gboolean ignore_this;
   gint i;
 
@@ -249,11 +249,11 @@ settings_backend_changed (GSettingsBackend    *backend,
 
 static void
 settings_backend_path_changed (GSettingsBackend *backend,
+                               GObject          *target,
                                const gchar      *path,
-                               gpointer          origin_tag,
-                               gpointer          user_data)
+                               gpointer          origin_tag)
 {
-  GSettings *settings = G_SETTINGS (user_data);
+  GSettings *settings = G_SETTINGS (target);
   gboolean ignore_this;
 
   g_assert (settings->priv->backend == backend);
@@ -265,12 +265,12 @@ settings_backend_path_changed (GSettingsBackend *backend,
 
 static void
 settings_backend_keys_changed (GSettingsBackend    *backend,
+                               GObject             *target,
                                const gchar         *path,
                                const gchar * const *items,
-                               gpointer             origin_tag,
-                               gpointer             user_data)
+                               gpointer             origin_tag)
 {
-  GSettings *settings = G_SETTINGS (user_data);
+  GSettings *settings = G_SETTINGS (target);
   gboolean ignore_this;
   gint i;
 
@@ -309,10 +309,10 @@ settings_backend_keys_changed (GSettingsBackend    *backend,
 
 static void
 settings_backend_writable_changed (GSettingsBackend *backend,
-                                   const gchar      *key,
-                                   gpointer          user_data)
+                                   GObject          *target,
+                                   const gchar      *key)
 {
-  GSettings *settings = G_SETTINGS (user_data);
+  GSettings *settings = G_SETTINGS (target);
   gboolean ignore_this;
   gint i;
 
@@ -328,10 +328,10 @@ settings_backend_writable_changed (GSettingsBackend *backend,
 
 static void
 settings_backend_path_writable_changed (GSettingsBackend *backend,
-                                        const gchar      *path,
-                                        gpointer          user_data)
+                                        GObject          *target,
+                                        const gchar      *path)
 {
-  GSettings *settings = G_SETTINGS (user_data);
+  GSettings *settings = G_SETTINGS (target);
   gboolean ignore_this;
 
   g_assert (settings->priv->backend == backend);
@@ -365,14 +365,13 @@ g_settings_constructed (GObject *object)
     }
 
   settings->priv->backend = g_settings_backend_get_with_context (settings->priv->context);
-  g_settings_backend_watch (settings->priv->backend,
+  g_settings_backend_watch (settings->priv->backend, G_OBJECT (settings),
                             settings->priv->main_context,
                             settings_backend_changed,
                             settings_backend_path_changed,
                             settings_backend_keys_changed,
                             settings_backend_writable_changed,
-                            settings_backend_path_writable_changed,
-                            settings);
+                            settings_backend_path_writable_changed);
   g_settings_backend_subscribe (settings->priv->backend,
                                 settings->priv->path);
 }
@@ -412,18 +411,17 @@ g_settings_delay (GSettings *settings)
 
   settings->priv->delayed =
     g_delayed_settings_backend_new (settings->priv->backend, settings);
-  g_settings_backend_unwatch (settings->priv->backend, settings);
+  g_settings_backend_unwatch (settings->priv->backend, G_OBJECT (settings));
   g_object_unref (settings->priv->backend);
 
   settings->priv->backend = G_SETTINGS_BACKEND (settings->priv->delayed);
-  g_settings_backend_watch (settings->priv->backend,
+  g_settings_backend_watch (settings->priv->backend, G_OBJECT (settings),
                             settings->priv->main_context,
                             settings_backend_changed,
                             settings_backend_path_changed,
                             settings_backend_keys_changed,
                             settings_backend_writable_changed,
-                            settings_backend_path_writable_changed,
-                            settings);
+                            settings_backend_path_writable_changed);
 }
 
 /**
@@ -546,7 +544,6 @@ g_settings_finalize (GObject *object)
 {
   GSettings *settings = G_SETTINGS (object);
 
-  g_settings_backend_unwatch (settings->priv->backend, settings);
   g_settings_backend_unsubscribe (settings->priv->backend,
                                   settings->priv->path);
   g_main_context_unref (settings->priv->main_context);
index 189b640..5679f57 100644 (file)
 
 G_DEFINE_ABSTRACT_TYPE (GSettingsBackend, g_settings_backend, G_TYPE_OBJECT)
 
-typedef struct _GSettingsBackendWatch GSettingsBackendWatch;
+typedef struct _GSettingsBackendClosure GSettingsBackendClosure;
+typedef struct _GSettingsBackendWatch   GSettingsBackendWatch;
 
 struct _GSettingsBackendPrivate
 {
   GSettingsBackendWatch *watches;
+  GStaticMutex lock;
   gchar *context;
 };
 
@@ -86,81 +88,6 @@ enum
  * </para></note>
  **/
 
-struct _GSettingsBackendWatch
-{
-  GMainContext                            *context;
-  GSettingsBackendChangedFunc              changed;
-  GSettingsBackendPathChangedFunc          path_changed;
-  GSettingsBackendKeysChangedFunc          keys_changed;
-  GSettingsBackendWritableChangedFunc      writable_changed;
-  GSettingsBackendPathWritableChangedFunc  path_writable_changed;
-  gpointer                                 user_data;
-
-  GSettingsBackendWatch                   *next;
-};
-
-/*< private >
- * g_settings_backend_watch:
- * @backend: a #GSettingsBackend
- * @context: a #GMainContext, or %NULL
- * ...: callbacks...
- * @user_data: the user_data for the callbacks
- *
- * Registers a new watch on a #GSettingsBackend.
- *
- * note: %NULL @context does not mean "default main context" but rather,
- * "it is okay to dispatch in any context".  If the default main context
- * is specifically desired then it must be given.
- **/
-void
-g_settings_backend_watch (GSettingsBackend                        *backend,
-                          GMainContext                            *context,
-                          GSettingsBackendChangedFunc              changed,
-                          GSettingsBackendPathChangedFunc          path_changed,
-                          GSettingsBackendKeysChangedFunc          keys_changed,
-                          GSettingsBackendWritableChangedFunc      writable_changed,
-                          GSettingsBackendPathWritableChangedFunc  path_writable_changed,
-                          gpointer                                 user_data)
-{
-  GSettingsBackendWatch *watch;
-
-  watch = g_slice_new (GSettingsBackendWatch);
-  /* don't need to ref the context here because the watch is
-   * only valid for the lifecycle of the attaching GSettings
-   * and it is already holding the context.
-   */
-  watch->context = context;
-  watch->changed = changed;
-  watch->path_changed = path_changed;
-  watch->keys_changed = keys_changed;
-  watch->writable_changed = writable_changed;
-  watch->path_writable_changed = path_writable_changed;
-  watch->user_data = user_data;
-
-  watch->next = backend->priv->watches;
-  backend->priv->watches = watch;
-}
-
-void
-g_settings_backend_unwatch (GSettingsBackend *backend,
-                            gpointer          user_data)
-{
-  GSettingsBackendWatch **ptr;
-
-  for (ptr = &backend->priv->watches; *ptr; ptr = &(*ptr)->next)
-    if ((*ptr)->user_data == user_data)
-      {
-        GSettingsBackendWatch *tmp = *ptr;
-
-        *ptr = tmp->next;
-        g_slice_free (GSettingsBackendWatch, tmp);
-
-        return;
-      }
-
-  g_assert_not_reached ();
-}
-
 static gboolean
 is_key (const gchar *key)
 {
@@ -219,93 +146,254 @@ g_settings_backend_get_active_context (void)
   return context;
 }
 
-typedef struct
+struct _GSettingsBackendWatch
+{
+  GObject                                 *target;
+  GMainContext                            *context;
+  GSettingsBackendChangedFunc              changed;
+  GSettingsBackendPathChangedFunc          path_changed;
+  GSettingsBackendKeysChangedFunc          keys_changed;
+  GSettingsBackendWritableChangedFunc      writable_changed;
+  GSettingsBackendPathWritableChangedFunc  path_writable_changed;
+
+  GSettingsBackendWatch                   *next;
+};
+
+struct _GSettingsBackendClosure
 {
   void (*function) (GSettingsBackend *backend,
+                    GObject          *target,
                     const gchar      *name,
                     gpointer          data1,
-                    gpointer          data2,
-                    gpointer          data3);
+                    gpointer          data2);
+
   GSettingsBackend *backend;
-  gchar *name;
+  GObject          *target;
+  gchar            *name;
+  gpointer          data1;
+  GBoxedFreeFunc    data1_free;
+  gpointer          data2;
+};
+
+static void
+g_settings_backend_watch_weak_notify (gpointer  data,
+                                      GObject  *where_object_was)
+{
+  GSettingsBackend *backend = data;
+  GSettingsBackendWatch **ptr;
+
+  /* search and remove */
+  g_static_mutex_lock (&backend->priv->lock);
+  for (ptr = &backend->priv->watches; *ptr; ptr = &(*ptr)->next)
+    if ((*ptr)->target == where_object_was)
+      {
+        GSettingsBackendWatch *tmp = *ptr;
+
+        *ptr = tmp->next;
+        g_slice_free (GSettingsBackendWatch, tmp);
+
+        g_static_mutex_unlock (&backend->priv->lock);
+        return;
+      }
 
-  gpointer data1;
-  gpointer data2;
-  gpointer data3;
+  /* we didn't find it.  that shouldn't happen. */
+  g_assert_not_reached ();
+}
 
-  GDestroyNotify destroy_data1;
-} GSettingsBackendClosure;
+/*< private >
+ * g_settings_backend_watch:
+ * @backend: a #GSettingsBackend
+ * @target: the GObject (typically GSettings instance) to call back to
+ * @context: a #GMainContext, or %NULL
+ * ...: callbacks...
+ *
+ * Registers a new watch on a #GSettingsBackend.
+ *
+ * note: %NULL @context does not mean "default main context" but rather,
+ * "it is okay to dispatch in any context".  If the default main context
+ * is specifically desired then it must be given.
+ *
+ * note also: if you want to get meaningful values for the @origin_tag
+ * that appears as an argument to some of the callbacks, you *must* have
+ * @context as %NULL.  Otherwise, you are subject to cross-thread
+ * dispatching and whatever owned @origin_tag at the time that the event
+ * occured may no longer own it.  This is a problem if you consider that
+ * you may now be the new owner of that address and mistakenly think
+ * that the event in question originated from yourself.
+ *
+ * tl;dr: If you give a non-%NULL @context then you must ignore the
+ * value of @origin_tag given to any callbacks.
+ **/
+void
+g_settings_backend_watch (GSettingsBackend                        *backend,
+                          GObject                                 *target,
+                          GMainContext                            *context,
+                          GSettingsBackendChangedFunc              changed,
+                          GSettingsBackendPathChangedFunc          path_changed,
+                          GSettingsBackendKeysChangedFunc          keys_changed,
+                          GSettingsBackendWritableChangedFunc      writable_changed,
+                          GSettingsBackendPathWritableChangedFunc  path_writable_changed)
+{
+  GSettingsBackendWatch *watch;
+
+  /* For purposes of discussion, we assume that our target is a
+   * GSettings instance.
+   *
+   * Our strategy to defend against the final reference dropping on the
+   * GSettings object in a thread other than the one that is doing the
+   * dispatching is as follows:
+   *
+   *  1) hold a GObject reference on the GSettings during an outstanding
+   *     dispatch.  This ensures that the delivery is always possible.
+   *
+   *  2) hold a weak reference on the GSettings at other times.  This
+   *     allows us to receive early notification of pending destruction
+   *     of the object.  At this point, it is still safe to obtain a
+   *     reference on the GObject to keep it alive, so #1 will work up
+   *     to that point.  After that point, we'll have been able to drop
+   *     the watch from the list.
+   *
+   * Note, in particular, that it's not possible to simply have an
+   * "unwatch" function that gets called from the finalize function of
+   * the GSettings instance because, by that point it is no longer
+   * possible to keep the object alive using g_object_ref() and we would
+   * have no way of knowing this.
+   *
+   * Note also that we do not need to hold a reference on the main
+   * context here since the GSettings instance does that for us and we
+   * will receive the weak notify long before it is dropped.  We don't
+   * even need to hold it during dispatches because our reference on the
+   * GSettings will prevent the finalize from running and dropping the
+   * ref on the context.
+   *
+   * All access to the list holds a mutex.  We have some strategies to
+   * avoid some of the pain that would be associated with that.
+   */
+  
+  watch = g_slice_new (GSettingsBackendWatch);
+  watch->context = context;
+  watch->target = target;
+  g_object_weak_ref (target, g_settings_backend_watch_weak_notify, backend);
+
+  watch->changed = changed;
+  watch->path_changed = path_changed;
+  watch->keys_changed = keys_changed;
+  watch->writable_changed = writable_changed;
+  watch->path_writable_changed = path_writable_changed;
+
+  /* linked list prepend */
+  g_static_mutex_lock (&backend->priv->lock);
+  watch->next = backend->priv->watches;
+  backend->priv->watches = watch;
+  g_static_mutex_unlock (&backend->priv->lock);
+}
+
+void
+g_settings_backend_unwatch (GSettingsBackend *backend,
+                            GObject          *target)
+{
+  /* Our caller surely owns a reference on 'target', so the order of
+   * these two calls is unimportant.
+   */
+  g_object_weak_unref (target, g_settings_backend_watch_weak_notify, backend);
+  g_settings_backend_watch_weak_notify (backend, target);
+}
 
 static gboolean
 g_settings_backend_invoke_closure (gpointer user_data)
 {
   GSettingsBackendClosure *closure = user_data;
 
-  closure->function (closure->backend, closure->name,
-                     closure->data1, closure->data2, closure->data3);
+  closure->function (closure->backend, closure->target, closure->name,
+                     closure->data1, closure->data2);
 
+  closure->data1_free (closure->data1);
   g_object_unref (closure->backend);
-
-  if (closure->destroy_data1)
-    closure->destroy_data1 (closure->data1);
-
+  g_object_unref (closure->target);
   g_free (closure->name);
 
-  /* make a donation to the magazine in this thread... */
   g_slice_free (GSettingsBackendClosure, closure);
 
   return FALSE;
 }
 
+static gpointer
+pointer_id (gpointer a)
+{
+  return a;
+}
+
+static void
+pointer_ignore (gpointer a)
+{
+}
+
 static void
 g_settings_backend_dispatch_signal (GSettingsBackend *backend,
-                                    GMainContext     *context,
-                                    gpointer          function,
+                                    gsize             function_offset,
                                     const gchar      *name,
                                     gpointer          data1,
-                                    GDestroyNotify    destroy_data1,
-                                    gpointer          data2,
-                                    gpointer          data3)
+                                    GBoxedCopyFunc    data1_copy,
+                                    GBoxedFreeFunc    data1_free,
+                                    gpointer          data2)
 {
-  GSettingsBackendClosure *closure;
-  GSource *source;
+  GMainContext *context, *here_and_now;
+  GSettingsBackendWatch *watch;
 
-  /* XXX we have a rather obnoxious race condition here.
-   *
-   * In the meantime of this call being dispatched to the other thread,
-   * the GSettings instance that is pointed to by the user_data may have
-   * been freed.
-   *
-   * There are two ways of handling this:
-   *
-   *   1) Ensure that the watch is still valid by the time it arrives in
-   *      the destination thread (potentially expensive)
-   *
-   *   2) Do proper refcounting (requires changing the interface).
+  /* We need to hold the mutex here (to prevent a node from being
+   * deleted as we are traversing the list).  Since we should not
+   * re-enter user code while holding this mutex, we create a
+   * one-time-use GMainContext and populate it with the events that we
+   * would have called directly.  We dispatch these events after
+   * releasing the lock.  Note that the GObject reference is acquired on
+   * the target while holding the mutex and the mutex needs to be held
+   * as part of the destruction of any GSettings instance (via the weak
+   * reference handling).  This is the key to the safety of the whole
+   * setup.
    */
-  closure = g_slice_new (GSettingsBackendClosure);
-  closure->backend = g_object_ref (backend);
-  closure->function = function;
 
-  /* we could make more effort to share this between all of the
-   * dispatches but it seems that it might be an overall loss in terms
-   * of performance/memory use and is definitely not worth the effort.
-   */
-  closure->name = g_strdup (name);
+  if (data1_copy == NULL)
+    data1_copy = pointer_id;
 
-  /* ditto. */
-  closure->data1 = data1;
-  closure->destroy_data1 = destroy_data1;
+  if (data1_free == NULL)
+    data1_free = pointer_ignore;
+  context = g_settings_backend_get_active_context ();
+  here_and_now = g_main_context_new ();
 
-  closure->data2 = data2;
-  closure->data3 = data3;
+  /* traverse the (immutable while holding lock) list */
+  g_static_mutex_lock (&backend->priv->lock);
+  for (watch = backend->priv->watches; watch; watch = watch->next)
+    {
+      GSettingsBackendClosure *closure;
+      GSource *source;
+
+      closure = g_slice_new (GSettingsBackendClosure);
+      closure->backend = g_object_ref (backend);
+      closure->target = g_object_ref (watch->target);
+      closure->function = G_STRUCT_MEMBER (void *, watch, function_offset);
+      closure->name = g_strdup (name);
+      closure->data1 = data1_copy (data1);
+      closure->data1_free = data1_free;
+      closure->data2 = data2;
+
+      source = g_idle_source_new ();
+      g_source_set_priority (source, G_PRIORITY_DEFAULT);
+      g_source_set_callback (source,
+                             g_settings_backend_invoke_closure,
+                             closure, NULL);
+
+      if (watch->context && watch->context != context)
+        g_source_attach (source, watch->context);
+      else
+        g_source_attach (source, here_and_now);
+
+      g_source_unref (source);
+    }
+  g_static_mutex_unlock (&backend->priv->lock);
 
-  source = g_idle_source_new ();
-  g_source_set_callback (source,
-                         g_settings_backend_invoke_closure,
-                         closure, NULL);
-  g_source_attach (source, context);
-  g_source_unref (source);
+  while (g_main_context_iteration (here_and_now, FALSE));
+  g_main_context_unref (here_and_now);
 }
 
 /**
@@ -344,21 +432,13 @@ g_settings_backend_changed (GSettingsBackend *backend,
                             const gchar      *key,
                             gpointer          origin_tag)
 {
-  GSettingsBackendWatch *watch;
-  GMainContext *context;
-
   g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
   g_return_if_fail (is_key (key));
 
-  context = g_settings_backend_get_active_context ();
-
-  for (watch = backend->priv->watches; watch; watch = watch->next)
-    if (watch->context == context || watch->context == NULL)
-      watch->changed (backend, key, origin_tag, watch->user_data);
-    else
-      g_settings_backend_dispatch_signal (backend, watch->context,
-                                          watch->changed, key, origin_tag,
-                                          NULL, watch->user_data, NULL);
+  g_settings_backend_dispatch_signal (backend,
+                                      G_STRUCT_OFFSET (GSettingsBackendWatch,
+                                                       changed),
+                                      key, origin_tag, NULL, NULL, NULL);
 }
 
 /**
@@ -398,14 +478,19 @@ g_settings_backend_keys_changed (GSettingsBackend    *backend,
                                  gchar const * const *items,
                                  gpointer             origin_tag)
 {
-  GSettingsBackendWatch *watch;
-
   g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
-  g_return_if_fail (path[0] == '\0' || is_path (path));
+  g_return_if_fail (is_path (path));
+
+  /* XXX: should do stricter checking (ie: inspect each item) */
   g_return_if_fail (items != NULL);
 
-  for (watch = backend->priv->watches; watch; watch = watch->next)
-    watch->keys_changed (backend, path, items, origin_tag, watch->user_data);
+  g_settings_backend_dispatch_signal (backend,
+                                      G_STRUCT_OFFSET (GSettingsBackendWatch,
+                                                       keys_changed),
+                                      path, (gpointer) items,
+                                      (GBoxedCopyFunc) g_strdupv,
+                                      (GBoxedFreeFunc) g_strfreev,
+                                      origin_tag);
 }
 
 /**
@@ -443,13 +528,13 @@ g_settings_backend_path_changed (GSettingsBackend *backend,
                                  const gchar      *path,
                                  gpointer          origin_tag)
 {
-  GSettingsBackendWatch *watch;
-
   g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
   g_return_if_fail (is_path (path));
 
-  for (watch = backend->priv->watches; watch; watch = watch->next)
-    watch->path_changed (backend, path, origin_tag, watch->user_data);
+  g_settings_backend_dispatch_signal (backend,
+                                      G_STRUCT_OFFSET (GSettingsBackendWatch,
+                                                       path_changed),
+                                      path, origin_tag, NULL, NULL, NULL);
 }
 
 /**
@@ -468,13 +553,13 @@ void
 g_settings_backend_writable_changed (GSettingsBackend *backend,
                                      const gchar      *key)
 {
-  GSettingsBackendWatch *watch;
-
   g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
   g_return_if_fail (is_key (key));
 
-  for (watch = backend->priv->watches; watch; watch = watch->next)
-    watch->writable_changed (backend, key, watch->user_data);
+  g_settings_backend_dispatch_signal (backend,
+                                      G_STRUCT_OFFSET (GSettingsBackendWatch,
+                                                       writable_changed),
+                                      key, NULL, NULL, NULL, NULL);
 }
 
 /**
@@ -494,13 +579,13 @@ void
 g_settings_backend_path_writable_changed (GSettingsBackend *backend,
                                           const gchar      *path)
 {
-  GSettingsBackendWatch *watch;
-
   g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
   g_return_if_fail (is_path (path));
 
-  for (watch = backend->priv->watches; watch; watch = watch->next)
-    watch->path_writable_changed (backend, path, watch->user_data);
+  g_settings_backend_dispatch_signal (backend,
+                                      G_STRUCT_OFFSET (GSettingsBackendWatch,
+                                                       path_writable_changed),
+                                      path, NULL, NULL, NULL, NULL);
 }
 
 typedef struct
@@ -636,7 +721,7 @@ g_settings_backend_changed_tree (GSettingsBackend *backend,
   g_settings_backend_flatten_tree (tree, &path, &keys, NULL);
 
   for (watch = backend->priv->watches; watch; watch = watch->next)
-    watch->keys_changed (backend, path, keys, origin_tag, watch->user_data);
+    watch->keys_changed (backend, watch->target, path, keys, origin_tag);
 
   g_free (path);
   g_free (keys);
@@ -866,9 +951,11 @@ g_settings_backend_finalize (GObject *object)
 {
   GSettingsBackend *backend = G_SETTINGS_BACKEND (object);
 
+  g_static_mutex_unlock (&backend->priv->lock);
   g_free (backend->priv->context);
 
-  G_OBJECT_CLASS (g_settings_backend_parent_class)->finalize (object);
+  G_OBJECT_CLASS (g_settings_backend_parent_class)
+    ->finalize (object);
 }
 
 static void
@@ -883,6 +970,7 @@ g_settings_backend_init (GSettingsBackend *backend)
   backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (backend,
                                                G_TYPE_SETTINGS_BACKEND,
                                                GSettingsBackendPrivate);
+  g_static_mutex_init (&backend->priv->lock);
 }
 
 static void
index 62867fb..b667983 100644 (file)
 #include "gsettingsbackend.h"
 
 typedef void          (*GSettingsBackendChangedFunc)                    (GSettingsBackend    *backend,
+                                                                         GObject             *target,
                                                                          const gchar         *key,
-                                                                         gpointer             origin_tag,
-                                                                         gpointer             user_data);
+                                                                         gpointer             origin_tag);
 typedef void          (*GSettingsBackendPathChangedFunc)                (GSettingsBackend    *backend,
+                                                                         GObject             *target,
                                                                          const gchar         *path,
-                                                                         gpointer             origin_tag,
-                                                                         gpointer             user_data);
+                                                                         gpointer             origin_tag);
 typedef void          (*GSettingsBackendKeysChangedFunc)                (GSettingsBackend    *backend,
+                                                                         GObject             *target,
                                                                          const gchar         *prefix,
                                                                          const gchar * const *names,
-                                                                         gpointer             origin_tag,
-                                                                         gpointer             user_data);
+                                                                         gpointer             origin_tag);
 typedef void          (*GSettingsBackendWritableChangedFunc)            (GSettingsBackend    *backend,
-                                                                         const gchar         *key,
-                                                                         gpointer             user_data);
+                                                                         GObject             *target,
+                                                                         const gchar         *key);
 typedef void          (*GSettingsBackendPathWritableChangedFunc)        (GSettingsBackend    *backend,
-                                                                         const gchar         *path,
-                                                                         gpointer             user_data);
+                                                                         GObject             *target,
+                                                                         const gchar         *path);
 
 G_GNUC_INTERNAL
 void                    g_settings_backend_watch                        (GSettingsBackend                        *backend,
+                                                                         GObject                                 *target,
                                                                          GMainContext                            *context,
                                                                          GSettingsBackendChangedFunc              changed,
                                                                          GSettingsBackendPathChangedFunc          path_changed,
                                                                          GSettingsBackendKeysChangedFunc          keys_changed,
                                                                          GSettingsBackendWritableChangedFunc      writable_changed,
-                                                                         GSettingsBackendPathWritableChangedFunc  path_writable_changed,
-                                                                         gpointer                                 user_data);
+                                                                         GSettingsBackendPathWritableChangedFunc  path_writable_changed);
 G_GNUC_INTERNAL
 void                    g_settings_backend_unwatch                      (GSettingsBackend                     *backend,
-                                                                         gpointer                              user_data);
+                                                                         GObject                              *target);
 
 G_GNUC_INTERNAL
 gboolean                        g_settings_backend_supports_context     (const gchar                          *context);