mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-02-04 10:16:17 +01:00
GSettingsBackend: make signal dispatch threadsafe
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.
This commit is contained in:
parent
4967b6d2ab
commit
61219e2640
@ -199,11 +199,11 @@ g_delayed_settings_backend_revert (GDelayedSettingsBackend *delayed)
|
|||||||
/* change notification */
|
/* change notification */
|
||||||
static void
|
static void
|
||||||
delayed_backend_changed (GSettingsBackend *backend,
|
delayed_backend_changed (GSettingsBackend *backend,
|
||||||
|
GObject *target,
|
||||||
const gchar *key,
|
const gchar *key,
|
||||||
gpointer origin_tag,
|
gpointer origin_tag)
|
||||||
gpointer user_data)
|
|
||||||
{
|
{
|
||||||
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
|
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
|
||||||
|
|
||||||
if (origin_tag != delayed->priv)
|
if (origin_tag != delayed->priv)
|
||||||
g_settings_backend_changed (G_SETTINGS_BACKEND (delayed),
|
g_settings_backend_changed (G_SETTINGS_BACKEND (delayed),
|
||||||
@ -212,12 +212,12 @@ delayed_backend_changed (GSettingsBackend *backend,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
delayed_backend_keys_changed (GSettingsBackend *backend,
|
delayed_backend_keys_changed (GSettingsBackend *backend,
|
||||||
|
GObject *target,
|
||||||
const gchar *path,
|
const gchar *path,
|
||||||
const gchar * const *items,
|
const gchar * const *items,
|
||||||
gpointer origin_tag,
|
gpointer origin_tag)
|
||||||
gpointer user_data)
|
|
||||||
{
|
{
|
||||||
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
|
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
|
||||||
|
|
||||||
if (origin_tag != delayed->priv)
|
if (origin_tag != delayed->priv)
|
||||||
g_settings_backend_keys_changed (G_SETTINGS_BACKEND (delayed),
|
g_settings_backend_keys_changed (G_SETTINGS_BACKEND (delayed),
|
||||||
@ -225,12 +225,12 @@ delayed_backend_keys_changed (GSettingsBackend *backend,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
delayed_backend_path_changed (GSettingsBackend *backend,
|
delayed_backend_path_changed (GSettingsBackend *backend,
|
||||||
const gchar *path,
|
GObject *target,
|
||||||
gpointer origin_tag,
|
const gchar *path,
|
||||||
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)
|
if (origin_tag != delayed->priv)
|
||||||
g_settings_backend_path_changed (G_SETTINGS_BACKEND (delayed),
|
g_settings_backend_path_changed (G_SETTINGS_BACKEND (delayed),
|
||||||
@ -239,10 +239,10 @@ delayed_backend_path_changed (GSettingsBackend *backend,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
delayed_backend_writable_changed (GSettingsBackend *backend,
|
delayed_backend_writable_changed (GSettingsBackend *backend,
|
||||||
const gchar *key,
|
GObject *target,
|
||||||
gpointer user_data)
|
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) &&
|
if (g_tree_lookup (delayed->priv->delayed, key) &&
|
||||||
!g_settings_backend_get_writable (delayed->priv->backend, key))
|
!g_settings_backend_get_writable (delayed->priv->backend, key))
|
||||||
@ -285,10 +285,10 @@ check_prefix (gpointer key,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
delayed_backend_path_writable_changed (GSettingsBackend *backend,
|
delayed_backend_path_writable_changed (GSettingsBackend *backend,
|
||||||
const gchar *path,
|
GObject *target,
|
||||||
gpointer user_data)
|
const gchar *path)
|
||||||
{
|
{
|
||||||
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
|
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
|
||||||
gsize n_keys;
|
gsize n_keys;
|
||||||
|
|
||||||
n_keys = g_tree_nnodes (delayed->priv->delayed);
|
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);
|
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (object);
|
||||||
|
|
||||||
g_settings_backend_unwatch (delayed->priv->backend, delayed);
|
|
||||||
g_object_unref (delayed->priv->backend);
|
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->backend = g_object_ref (backend);
|
||||||
delayed->priv->owner = owner;
|
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_changed,
|
||||||
delayed_backend_path_changed,
|
delayed_backend_path_changed,
|
||||||
delayed_backend_keys_changed,
|
delayed_backend_keys_changed,
|
||||||
delayed_backend_writable_changed,
|
delayed_backend_writable_changed,
|
||||||
delayed_backend_path_writable_changed,
|
delayed_backend_path_writable_changed);
|
||||||
delayed);
|
|
||||||
|
|
||||||
return delayed;
|
return delayed;
|
||||||
}
|
}
|
||||||
|
@ -224,11 +224,11 @@ g_settings_real_writable_change_event (GSettings *settings,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
settings_backend_changed (GSettingsBackend *backend,
|
settings_backend_changed (GSettingsBackend *backend,
|
||||||
|
GObject *target,
|
||||||
const gchar *key,
|
const gchar *key,
|
||||||
gpointer origin_tag,
|
gpointer origin_tag)
|
||||||
gpointer user_data)
|
|
||||||
{
|
{
|
||||||
GSettings *settings = G_SETTINGS (user_data);
|
GSettings *settings = G_SETTINGS (target);
|
||||||
gboolean ignore_this;
|
gboolean ignore_this;
|
||||||
gint i;
|
gint i;
|
||||||
|
|
||||||
@ -249,11 +249,11 @@ settings_backend_changed (GSettingsBackend *backend,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
settings_backend_path_changed (GSettingsBackend *backend,
|
settings_backend_path_changed (GSettingsBackend *backend,
|
||||||
|
GObject *target,
|
||||||
const gchar *path,
|
const gchar *path,
|
||||||
gpointer origin_tag,
|
gpointer origin_tag)
|
||||||
gpointer user_data)
|
|
||||||
{
|
{
|
||||||
GSettings *settings = G_SETTINGS (user_data);
|
GSettings *settings = G_SETTINGS (target);
|
||||||
gboolean ignore_this;
|
gboolean ignore_this;
|
||||||
|
|
||||||
g_assert (settings->priv->backend == backend);
|
g_assert (settings->priv->backend == backend);
|
||||||
@ -265,12 +265,12 @@ settings_backend_path_changed (GSettingsBackend *backend,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
settings_backend_keys_changed (GSettingsBackend *backend,
|
settings_backend_keys_changed (GSettingsBackend *backend,
|
||||||
|
GObject *target,
|
||||||
const gchar *path,
|
const gchar *path,
|
||||||
const gchar * const *items,
|
const gchar * const *items,
|
||||||
gpointer origin_tag,
|
gpointer origin_tag)
|
||||||
gpointer user_data)
|
|
||||||
{
|
{
|
||||||
GSettings *settings = G_SETTINGS (user_data);
|
GSettings *settings = G_SETTINGS (target);
|
||||||
gboolean ignore_this;
|
gboolean ignore_this;
|
||||||
gint i;
|
gint i;
|
||||||
|
|
||||||
@ -309,10 +309,10 @@ settings_backend_keys_changed (GSettingsBackend *backend,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
settings_backend_writable_changed (GSettingsBackend *backend,
|
settings_backend_writable_changed (GSettingsBackend *backend,
|
||||||
const gchar *key,
|
GObject *target,
|
||||||
gpointer user_data)
|
const gchar *key)
|
||||||
{
|
{
|
||||||
GSettings *settings = G_SETTINGS (user_data);
|
GSettings *settings = G_SETTINGS (target);
|
||||||
gboolean ignore_this;
|
gboolean ignore_this;
|
||||||
gint i;
|
gint i;
|
||||||
|
|
||||||
@ -328,10 +328,10 @@ settings_backend_writable_changed (GSettingsBackend *backend,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
settings_backend_path_writable_changed (GSettingsBackend *backend,
|
settings_backend_path_writable_changed (GSettingsBackend *backend,
|
||||||
const gchar *path,
|
GObject *target,
|
||||||
gpointer user_data)
|
const gchar *path)
|
||||||
{
|
{
|
||||||
GSettings *settings = G_SETTINGS (user_data);
|
GSettings *settings = G_SETTINGS (target);
|
||||||
gboolean ignore_this;
|
gboolean ignore_this;
|
||||||
|
|
||||||
g_assert (settings->priv->backend == backend);
|
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);
|
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->priv->main_context,
|
||||||
settings_backend_changed,
|
settings_backend_changed,
|
||||||
settings_backend_path_changed,
|
settings_backend_path_changed,
|
||||||
settings_backend_keys_changed,
|
settings_backend_keys_changed,
|
||||||
settings_backend_writable_changed,
|
settings_backend_writable_changed,
|
||||||
settings_backend_path_writable_changed,
|
settings_backend_path_writable_changed);
|
||||||
settings);
|
|
||||||
g_settings_backend_subscribe (settings->priv->backend,
|
g_settings_backend_subscribe (settings->priv->backend,
|
||||||
settings->priv->path);
|
settings->priv->path);
|
||||||
}
|
}
|
||||||
@ -412,18 +411,17 @@ g_settings_delay (GSettings *settings)
|
|||||||
|
|
||||||
settings->priv->delayed =
|
settings->priv->delayed =
|
||||||
g_delayed_settings_backend_new (settings->priv->backend, settings);
|
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);
|
g_object_unref (settings->priv->backend);
|
||||||
|
|
||||||
settings->priv->backend = G_SETTINGS_BACKEND (settings->priv->delayed);
|
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->priv->main_context,
|
||||||
settings_backend_changed,
|
settings_backend_changed,
|
||||||
settings_backend_path_changed,
|
settings_backend_path_changed,
|
||||||
settings_backend_keys_changed,
|
settings_backend_keys_changed,
|
||||||
settings_backend_writable_changed,
|
settings_backend_writable_changed,
|
||||||
settings_backend_path_writable_changed,
|
settings_backend_path_writable_changed);
|
||||||
settings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -546,7 +544,6 @@ g_settings_finalize (GObject *object)
|
|||||||
{
|
{
|
||||||
GSettings *settings = G_SETTINGS (object);
|
GSettings *settings = G_SETTINGS (object);
|
||||||
|
|
||||||
g_settings_backend_unwatch (settings->priv->backend, settings);
|
|
||||||
g_settings_backend_unsubscribe (settings->priv->backend,
|
g_settings_backend_unsubscribe (settings->priv->backend,
|
||||||
settings->priv->path);
|
settings->priv->path);
|
||||||
g_main_context_unref (settings->priv->main_context);
|
g_main_context_unref (settings->priv->main_context);
|
||||||
|
@ -37,11 +37,13 @@
|
|||||||
|
|
||||||
G_DEFINE_ABSTRACT_TYPE (GSettingsBackend, g_settings_backend, G_TYPE_OBJECT)
|
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
|
struct _GSettingsBackendPrivate
|
||||||
{
|
{
|
||||||
GSettingsBackendWatch *watches;
|
GSettingsBackendWatch *watches;
|
||||||
|
GStaticMutex lock;
|
||||||
gchar *context;
|
gchar *context;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -86,81 +88,6 @@ enum
|
|||||||
* </para></note>
|
* </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
|
static gboolean
|
||||||
is_key (const gchar *key)
|
is_key (const gchar *key)
|
||||||
{
|
{
|
||||||
@ -219,93 +146,254 @@ g_settings_backend_get_active_context (void)
|
|||||||
return context;
|
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,
|
void (*function) (GSettingsBackend *backend,
|
||||||
|
GObject *target,
|
||||||
const gchar *name,
|
const gchar *name,
|
||||||
gpointer data1,
|
gpointer data1,
|
||||||
gpointer data2,
|
gpointer data2);
|
||||||
gpointer data3);
|
|
||||||
GSettingsBackend *backend;
|
GSettingsBackend *backend;
|
||||||
gchar *name;
|
GObject *target;
|
||||||
|
gchar *name;
|
||||||
|
gpointer data1;
|
||||||
|
GBoxedFreeFunc data1_free;
|
||||||
|
gpointer data2;
|
||||||
|
};
|
||||||
|
|
||||||
gpointer data1;
|
static void
|
||||||
gpointer data2;
|
g_settings_backend_watch_weak_notify (gpointer data,
|
||||||
gpointer data3;
|
GObject *where_object_was)
|
||||||
|
{
|
||||||
|
GSettingsBackend *backend = data;
|
||||||
|
GSettingsBackendWatch **ptr;
|
||||||
|
|
||||||
GDestroyNotify destroy_data1;
|
/* search and remove */
|
||||||
} GSettingsBackendClosure;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we didn't find it. that shouldn't happen. */
|
||||||
|
g_assert_not_reached ();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*< 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
|
static gboolean
|
||||||
g_settings_backend_invoke_closure (gpointer user_data)
|
g_settings_backend_invoke_closure (gpointer user_data)
|
||||||
{
|
{
|
||||||
GSettingsBackendClosure *closure = user_data;
|
GSettingsBackendClosure *closure = user_data;
|
||||||
|
|
||||||
closure->function (closure->backend, closure->name,
|
closure->function (closure->backend, closure->target, closure->name,
|
||||||
closure->data1, closure->data2, closure->data3);
|
closure->data1, closure->data2);
|
||||||
|
|
||||||
|
closure->data1_free (closure->data1);
|
||||||
g_object_unref (closure->backend);
|
g_object_unref (closure->backend);
|
||||||
|
g_object_unref (closure->target);
|
||||||
if (closure->destroy_data1)
|
|
||||||
closure->destroy_data1 (closure->data1);
|
|
||||||
|
|
||||||
g_free (closure->name);
|
g_free (closure->name);
|
||||||
|
|
||||||
/* make a donation to the magazine in this thread... */
|
|
||||||
g_slice_free (GSettingsBackendClosure, closure);
|
g_slice_free (GSettingsBackendClosure, closure);
|
||||||
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gpointer
|
||||||
|
pointer_id (gpointer a)
|
||||||
|
{
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pointer_ignore (gpointer a)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
g_settings_backend_dispatch_signal (GSettingsBackend *backend,
|
g_settings_backend_dispatch_signal (GSettingsBackend *backend,
|
||||||
GMainContext *context,
|
gsize function_offset,
|
||||||
gpointer function,
|
|
||||||
const gchar *name,
|
const gchar *name,
|
||||||
gpointer data1,
|
gpointer data1,
|
||||||
GDestroyNotify destroy_data1,
|
GBoxedCopyFunc data1_copy,
|
||||||
gpointer data2,
|
GBoxedFreeFunc data1_free,
|
||||||
gpointer data3)
|
gpointer data2)
|
||||||
{
|
{
|
||||||
GSettingsBackendClosure *closure;
|
GMainContext *context, *here_and_now;
|
||||||
GSource *source;
|
GSettingsBackendWatch *watch;
|
||||||
|
|
||||||
/* XXX we have a rather obnoxious race condition here.
|
/* We need to hold the mutex here (to prevent a node from being
|
||||||
*
|
* deleted as we are traversing the list). Since we should not
|
||||||
* In the meantime of this call being dispatched to the other thread,
|
* re-enter user code while holding this mutex, we create a
|
||||||
* the GSettings instance that is pointed to by the user_data may have
|
* one-time-use GMainContext and populate it with the events that we
|
||||||
* been freed.
|
* would have called directly. We dispatch these events after
|
||||||
*
|
* releasing the lock. Note that the GObject reference is acquired on
|
||||||
* There are two ways of handling this:
|
* 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
|
||||||
* 1) Ensure that the watch is still valid by the time it arrives in
|
* reference handling). This is the key to the safety of the whole
|
||||||
* the destination thread (potentially expensive)
|
* setup.
|
||||||
*
|
|
||||||
* 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
|
if (data1_copy == NULL)
|
||||||
* dispatches but it seems that it might be an overall loss in terms
|
data1_copy = pointer_id;
|
||||||
* of performance/memory use and is definitely not worth the effort.
|
|
||||||
*/
|
|
||||||
closure->name = g_strdup (name);
|
|
||||||
|
|
||||||
/* ditto. */
|
if (data1_free == NULL)
|
||||||
closure->data1 = data1;
|
data1_free = pointer_ignore;
|
||||||
closure->destroy_data1 = destroy_data1;
|
|
||||||
|
context = g_settings_backend_get_active_context ();
|
||||||
|
here_and_now = g_main_context_new ();
|
||||||
|
|
||||||
closure->data2 = data2;
|
/* traverse the (immutable while holding lock) list */
|
||||||
closure->data3 = data3;
|
g_static_mutex_lock (&backend->priv->lock);
|
||||||
|
for (watch = backend->priv->watches; watch; watch = watch->next)
|
||||||
|
{
|
||||||
|
GSettingsBackendClosure *closure;
|
||||||
|
GSource *source;
|
||||||
|
|
||||||
source = g_idle_source_new ();
|
closure = g_slice_new (GSettingsBackendClosure);
|
||||||
g_source_set_callback (source,
|
closure->backend = g_object_ref (backend);
|
||||||
g_settings_backend_invoke_closure,
|
closure->target = g_object_ref (watch->target);
|
||||||
closure, NULL);
|
closure->function = G_STRUCT_MEMBER (void *, watch, function_offset);
|
||||||
g_source_attach (source, context);
|
closure->name = g_strdup (name);
|
||||||
g_source_unref (source);
|
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);
|
||||||
|
|
||||||
|
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,
|
const gchar *key,
|
||||||
gpointer origin_tag)
|
gpointer origin_tag)
|
||||||
{
|
{
|
||||||
GSettingsBackendWatch *watch;
|
|
||||||
GMainContext *context;
|
|
||||||
|
|
||||||
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
|
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
|
||||||
g_return_if_fail (is_key (key));
|
g_return_if_fail (is_key (key));
|
||||||
|
|
||||||
context = g_settings_backend_get_active_context ();
|
g_settings_backend_dispatch_signal (backend,
|
||||||
|
G_STRUCT_OFFSET (GSettingsBackendWatch,
|
||||||
for (watch = backend->priv->watches; watch; watch = watch->next)
|
changed),
|
||||||
if (watch->context == context || watch->context == NULL)
|
key, origin_tag, NULL, NULL, 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -398,14 +478,19 @@ g_settings_backend_keys_changed (GSettingsBackend *backend,
|
|||||||
gchar const * const *items,
|
gchar const * const *items,
|
||||||
gpointer origin_tag)
|
gpointer origin_tag)
|
||||||
{
|
{
|
||||||
GSettingsBackendWatch *watch;
|
|
||||||
|
|
||||||
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
|
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);
|
g_return_if_fail (items != NULL);
|
||||||
|
|
||||||
for (watch = backend->priv->watches; watch; watch = watch->next)
|
g_settings_backend_dispatch_signal (backend,
|
||||||
watch->keys_changed (backend, path, items, origin_tag, watch->user_data);
|
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,
|
const gchar *path,
|
||||||
gpointer origin_tag)
|
gpointer origin_tag)
|
||||||
{
|
{
|
||||||
GSettingsBackendWatch *watch;
|
|
||||||
|
|
||||||
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
|
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
|
||||||
g_return_if_fail (is_path (path));
|
g_return_if_fail (is_path (path));
|
||||||
|
|
||||||
for (watch = backend->priv->watches; watch; watch = watch->next)
|
g_settings_backend_dispatch_signal (backend,
|
||||||
watch->path_changed (backend, path, origin_tag, watch->user_data);
|
G_STRUCT_OFFSET (GSettingsBackendWatch,
|
||||||
|
path_changed),
|
||||||
|
path, origin_tag, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -468,13 +553,13 @@ void
|
|||||||
g_settings_backend_writable_changed (GSettingsBackend *backend,
|
g_settings_backend_writable_changed (GSettingsBackend *backend,
|
||||||
const gchar *key)
|
const gchar *key)
|
||||||
{
|
{
|
||||||
GSettingsBackendWatch *watch;
|
|
||||||
|
|
||||||
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
|
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
|
||||||
g_return_if_fail (is_key (key));
|
g_return_if_fail (is_key (key));
|
||||||
|
|
||||||
for (watch = backend->priv->watches; watch; watch = watch->next)
|
g_settings_backend_dispatch_signal (backend,
|
||||||
watch->writable_changed (backend, key, watch->user_data);
|
G_STRUCT_OFFSET (GSettingsBackendWatch,
|
||||||
|
writable_changed),
|
||||||
|
key, NULL, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -494,13 +579,13 @@ void
|
|||||||
g_settings_backend_path_writable_changed (GSettingsBackend *backend,
|
g_settings_backend_path_writable_changed (GSettingsBackend *backend,
|
||||||
const gchar *path)
|
const gchar *path)
|
||||||
{
|
{
|
||||||
GSettingsBackendWatch *watch;
|
|
||||||
|
|
||||||
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
|
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
|
||||||
g_return_if_fail (is_path (path));
|
g_return_if_fail (is_path (path));
|
||||||
|
|
||||||
for (watch = backend->priv->watches; watch; watch = watch->next)
|
g_settings_backend_dispatch_signal (backend,
|
||||||
watch->path_writable_changed (backend, path, watch->user_data);
|
G_STRUCT_OFFSET (GSettingsBackendWatch,
|
||||||
|
path_writable_changed),
|
||||||
|
path, NULL, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
@ -636,7 +721,7 @@ g_settings_backend_changed_tree (GSettingsBackend *backend,
|
|||||||
g_settings_backend_flatten_tree (tree, &path, &keys, NULL);
|
g_settings_backend_flatten_tree (tree, &path, &keys, NULL);
|
||||||
|
|
||||||
for (watch = backend->priv->watches; watch; watch = watch->next)
|
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 (path);
|
||||||
g_free (keys);
|
g_free (keys);
|
||||||
@ -866,9 +951,11 @@ g_settings_backend_finalize (GObject *object)
|
|||||||
{
|
{
|
||||||
GSettingsBackend *backend = G_SETTINGS_BACKEND (object);
|
GSettingsBackend *backend = G_SETTINGS_BACKEND (object);
|
||||||
|
|
||||||
|
g_static_mutex_unlock (&backend->priv->lock);
|
||||||
g_free (backend->priv->context);
|
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
|
static void
|
||||||
@ -883,6 +970,7 @@ g_settings_backend_init (GSettingsBackend *backend)
|
|||||||
backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (backend,
|
backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (backend,
|
||||||
G_TYPE_SETTINGS_BACKEND,
|
G_TYPE_SETTINGS_BACKEND,
|
||||||
GSettingsBackendPrivate);
|
GSettingsBackendPrivate);
|
||||||
|
g_static_mutex_init (&backend->priv->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -27,37 +27,37 @@
|
|||||||
#include "gsettingsbackend.h"
|
#include "gsettingsbackend.h"
|
||||||
|
|
||||||
typedef void (*GSettingsBackendChangedFunc) (GSettingsBackend *backend,
|
typedef void (*GSettingsBackendChangedFunc) (GSettingsBackend *backend,
|
||||||
|
GObject *target,
|
||||||
const gchar *key,
|
const gchar *key,
|
||||||
gpointer origin_tag,
|
gpointer origin_tag);
|
||||||
gpointer user_data);
|
|
||||||
typedef void (*GSettingsBackendPathChangedFunc) (GSettingsBackend *backend,
|
typedef void (*GSettingsBackendPathChangedFunc) (GSettingsBackend *backend,
|
||||||
|
GObject *target,
|
||||||
const gchar *path,
|
const gchar *path,
|
||||||
gpointer origin_tag,
|
gpointer origin_tag);
|
||||||
gpointer user_data);
|
|
||||||
typedef void (*GSettingsBackendKeysChangedFunc) (GSettingsBackend *backend,
|
typedef void (*GSettingsBackendKeysChangedFunc) (GSettingsBackend *backend,
|
||||||
|
GObject *target,
|
||||||
const gchar *prefix,
|
const gchar *prefix,
|
||||||
const gchar * const *names,
|
const gchar * const *names,
|
||||||
gpointer origin_tag,
|
gpointer origin_tag);
|
||||||
gpointer user_data);
|
|
||||||
typedef void (*GSettingsBackendWritableChangedFunc) (GSettingsBackend *backend,
|
typedef void (*GSettingsBackendWritableChangedFunc) (GSettingsBackend *backend,
|
||||||
const gchar *key,
|
GObject *target,
|
||||||
gpointer user_data);
|
const gchar *key);
|
||||||
typedef void (*GSettingsBackendPathWritableChangedFunc) (GSettingsBackend *backend,
|
typedef void (*GSettingsBackendPathWritableChangedFunc) (GSettingsBackend *backend,
|
||||||
const gchar *path,
|
GObject *target,
|
||||||
gpointer user_data);
|
const gchar *path);
|
||||||
|
|
||||||
G_GNUC_INTERNAL
|
G_GNUC_INTERNAL
|
||||||
void g_settings_backend_watch (GSettingsBackend *backend,
|
void g_settings_backend_watch (GSettingsBackend *backend,
|
||||||
|
GObject *target,
|
||||||
GMainContext *context,
|
GMainContext *context,
|
||||||
GSettingsBackendChangedFunc changed,
|
GSettingsBackendChangedFunc changed,
|
||||||
GSettingsBackendPathChangedFunc path_changed,
|
GSettingsBackendPathChangedFunc path_changed,
|
||||||
GSettingsBackendKeysChangedFunc keys_changed,
|
GSettingsBackendKeysChangedFunc keys_changed,
|
||||||
GSettingsBackendWritableChangedFunc writable_changed,
|
GSettingsBackendWritableChangedFunc writable_changed,
|
||||||
GSettingsBackendPathWritableChangedFunc path_writable_changed,
|
GSettingsBackendPathWritableChangedFunc path_writable_changed);
|
||||||
gpointer user_data);
|
|
||||||
G_GNUC_INTERNAL
|
G_GNUC_INTERNAL
|
||||||
void g_settings_backend_unwatch (GSettingsBackend *backend,
|
void g_settings_backend_unwatch (GSettingsBackend *backend,
|
||||||
gpointer user_data);
|
GObject *target);
|
||||||
|
|
||||||
G_GNUC_INTERNAL
|
G_GNUC_INTERNAL
|
||||||
gboolean g_settings_backend_supports_context (const gchar *context);
|
gboolean g_settings_backend_supports_context (const gchar *context);
|
||||||
|
Loading…
Reference in New Issue
Block a user