[glib] GSettings: support emitting signals in threads



commit 984258c662d3f571fcd0ea415923aec7a3746826
Author: Ryan Lortie <desrt desrt ca>
Date:   Sun May 16 13:02:23 2010 +0200

    GSettings: support emitting signals in threads
    
    The thread-default context that was in effect at the time that the
    GSettings was created will be used for emitting signals on that
    GSettings.

 gio/gdelayedsettingsbackend.c  |    2 +-
 gio/gsettings.c                |   18 +++++
 gio/gsettingsbackend.c         |  139 +++++++++++++++++++++++++++++++++++++++-
 gio/gsettingsbackendinternal.h |    2 +
 4 files changed, 159 insertions(+), 2 deletions(-)
---
diff --git a/gio/gdelayedsettingsbackend.c b/gio/gdelayedsettingsbackend.c
index c09be7f..ed18058 100644
--- a/gio/gdelayedsettingsbackend.c
+++ b/gio/gdelayedsettingsbackend.c
@@ -367,7 +367,7 @@ 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,
+  g_settings_backend_watch (delayed->priv->backend, NULL,
                             delayed_backend_changed,
                             delayed_backend_path_changed,
                             delayed_backend_keys_changed,
diff --git a/gio/gsettings.c b/gio/gsettings.c
index 5b894e6..aa465ea 100644
--- a/gio/gsettings.c
+++ b/gio/gsettings.c
@@ -146,6 +146,9 @@
 
 struct _GSettingsPrivate
 {
+  /* where the signals go... */
+  GMainContext *main_context;
+
   GSettingsBackend *backend;
   GSettingsSchema *schema;
   gchar *schema_name;
@@ -363,6 +366,7 @@ g_settings_constructed (GObject *object)
 
   settings->priv->backend = g_settings_backend_get_with_context (settings->priv->context);
   g_settings_backend_watch (settings->priv->backend,
+                            settings->priv->main_context,
                             settings_backend_changed,
                             settings_backend_path_changed,
                             settings_backend_keys_changed,
@@ -379,6 +383,13 @@ g_settings_init (GSettings *settings)
   settings->priv = G_TYPE_INSTANCE_GET_PRIVATE (settings,
                                                 G_TYPE_SETTINGS,
                                                 GSettingsPrivate);
+
+  settings->priv->main_context = g_main_context_get_thread_default ();
+
+  if (settings->priv->main_context == NULL)
+    settings->priv->main_context = g_main_context_default ();
+
+  g_main_context_ref (settings->priv->main_context);
 }
 
 /**
@@ -406,6 +417,7 @@ g_settings_delay (GSettings *settings)
 
   settings->priv->backend = G_SETTINGS_BACKEND (settings->priv->delayed);
   g_settings_backend_watch (settings->priv->backend,
+                            settings->priv->main_context,
                             settings_backend_changed,
                             settings_backend_path_changed,
                             settings_backend_keys_changed,
@@ -537,6 +549,7 @@ g_settings_finalize (GObject *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);
   g_object_unref (settings->priv->backend);
   g_object_unref (settings->priv->schema);
   g_free (settings->priv->schema_name);
@@ -1016,6 +1029,11 @@ g_settings_get_child (GSettings   *settings,
  *
  * Creates a new #GSettings object with a given schema.
  *
+ * Signals on the newly created #GSettings object will be dispatched
+ * via the thread-default #GMainContext in effect at the time of the
+ * call to g_settings_new().  The new #GSettings will hold a reference
+ * on the context.  See g_main_context_push_thread_default().
+ *
  * Since: 2.26
  */
 GSettings *
diff --git a/gio/gsettingsbackend.c b/gio/gsettingsbackend.c
index b5699b4..189b640 100644
--- a/gio/gsettingsbackend.c
+++ b/gio/gsettingsbackend.c
@@ -88,6 +88,7 @@ enum
 
 struct _GSettingsBackendWatch
 {
+  GMainContext                            *context;
   GSettingsBackendChangedFunc              changed;
   GSettingsBackendPathChangedFunc          path_changed;
   GSettingsBackendKeysChangedFunc          keys_changed;
@@ -98,8 +99,22 @@ struct _GSettingsBackendWatch
   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,
@@ -110,6 +125,11 @@ g_settings_backend_watch (GSettingsBackend                        *backend,
   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;
@@ -179,6 +199,115 @@ is_path (const gchar *path)
   return TRUE;
 }
 
+static GMainContext *
+g_settings_backend_get_active_context (void)
+{
+  GMainContext *context;
+  GSource *source;
+
+  if ((source = g_main_current_source ()))
+    context = g_source_get_context (source);
+
+  else
+    {
+      context = g_main_context_get_thread_default ();
+
+      if (context == NULL)
+        context = g_main_context_default ();
+    }
+
+  return context;
+}
+
+typedef struct
+{
+  void (*function) (GSettingsBackend *backend,
+                    const gchar      *name,
+                    gpointer          data1,
+                    gpointer          data2,
+                    gpointer          data3);
+  GSettingsBackend *backend;
+  gchar *name;
+
+  gpointer data1;
+  gpointer data2;
+  gpointer data3;
+
+  GDestroyNotify destroy_data1;
+} GSettingsBackendClosure;
+
+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);
+
+  g_object_unref (closure->backend);
+
+  if (closure->destroy_data1)
+    closure->destroy_data1 (closure->data1);
+
+  g_free (closure->name);
+
+  /* make a donation to the magazine in this thread... */
+  g_slice_free (GSettingsBackendClosure, closure);
+
+  return FALSE;
+}
+
+static void
+g_settings_backend_dispatch_signal (GSettingsBackend *backend,
+                                    GMainContext     *context,
+                                    gpointer          function,
+                                    const gchar      *name,
+                                    gpointer          data1,
+                                    GDestroyNotify    destroy_data1,
+                                    gpointer          data2,
+                                    gpointer          data3)
+{
+  GSettingsBackendClosure *closure;
+  GSource *source;
+
+  /* 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).
+   */
+  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);
+
+  /* ditto. */
+  closure->data1 = data1;
+  closure->destroy_data1 = destroy_data1;
+
+  closure->data2 = data2;
+  closure->data3 = data3;
+
+  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);
+}
+
 /**
  * g_settings_backend_changed:
  * @backend: a #GSettingsBackend implementation
@@ -216,12 +345,20 @@ g_settings_backend_changed (GSettingsBackend *backend,
                             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)
-    watch->changed (backend, key, origin_tag, watch->user_data);
+    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);
 }
 
 /**
diff --git a/gio/gsettingsbackendinternal.h b/gio/gsettingsbackendinternal.h
index b36eaa3..62867fb 100644
--- a/gio/gsettingsbackendinternal.h
+++ b/gio/gsettingsbackendinternal.h
@@ -48,6 +48,7 @@ typedef void          (*GSettingsBackendPathWritableChangedFunc)        (GSettin
 
 G_GNUC_INTERNAL
 void                    g_settings_backend_watch                        (GSettingsBackend                        *backend,
+                                                                         GMainContext                            *context,
                                                                          GSettingsBackendChangedFunc              changed,
                                                                          GSettingsBackendPathChangedFunc          path_changed,
                                                                          GSettingsBackendKeysChangedFunc          keys_changed,
@@ -97,4 +98,5 @@ void                            g_settings_backend_unsubscribe          (GSettin
 G_GNUC_INTERNAL
 void                            g_settings_backend_subscribe            (GSettingsBackend                     *backend,
                                                                          const char                           *name);
+
 #endif  /* __G_SETTINGS_BACKEND_INTERNAL_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]