mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-26 15:36:14 +01:00
gdbus-codegen: Ensure that generated skeletons are MT-safe
For example, if setting a property on a skeleton from another thread than where it was constructed, the idle handler responsible for emitting the PropertiesChanged() signal could run immediately and clear skeleton->priv->changed_properties_idle_source causing g_source_unref() to be called with a NULL pointer. This race was easily be fixed by adding a lock to the skeleton object. In addition to fixing this race, also move the code for setting up the idle handler to a class handler for the GObject::notify signal. This change allows use of g_object_freeze_notify() and g_object_thaw_notify() to perform atomic property changes from another thread than the one that the skeleton was created in. Signed-off-by: David Zeuthen <davidz@redhat.com>
This commit is contained in:
parent
2122191595
commit
11e01802ab
@ -711,12 +711,17 @@ on_handle_hello_world (MyAppFrobber *object,
|
|||||||
<para>
|
<para>
|
||||||
To facilitate atomic changesets (multiple properties changing at
|
To facilitate atomic changesets (multiple properties changing at
|
||||||
the same time), #GObject::notify signals are queued up when
|
the same time), #GObject::notify signals are queued up when
|
||||||
received. The queue is drained in an idle handler and will cause
|
received. The queue is drained in an idle handler (which is called from the
|
||||||
emissions of the <ulink
|
<link linkend="g-main-context-push-thread-default">thread-default main loop</link>
|
||||||
|
of the thread where the skeleton object was
|
||||||
|
contructed) and will cause emissions of the <ulink
|
||||||
url="http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties">org.freedesktop.DBus.Properties::PropertiesChanged</ulink>
|
url="http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties">org.freedesktop.DBus.Properties::PropertiesChanged</ulink>
|
||||||
signal with all the properties that have changed. Use
|
signal with all the properties that have changed. Use
|
||||||
g_dbus_interface_skeleton_flush() or
|
g_dbus_interface_skeleton_flush() or
|
||||||
g_dbus_object_skeleton_flush() to empty the queue immediately.
|
g_dbus_object_skeleton_flush() to empty the queue
|
||||||
|
immediately. Use g_object_freeze_notify() and
|
||||||
|
g_object_thaw_notify() for atomic changesets if on a different
|
||||||
|
thread.
|
||||||
</para>
|
</para>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
@ -1845,6 +1845,7 @@ class CodeGenerator:
|
|||||||
' GList *changed_properties;\n'
|
' GList *changed_properties;\n'
|
||||||
' GSource *changed_properties_idle_source;\n'
|
' GSource *changed_properties_idle_source;\n'
|
||||||
' GMainContext *context;\n'
|
' GMainContext *context;\n'
|
||||||
|
' GMutex *lock;\n'
|
||||||
'};\n'
|
'};\n'
|
||||||
'\n'%i.camel_name)
|
'\n'%i.camel_name)
|
||||||
|
|
||||||
@ -2054,12 +2055,19 @@ class CodeGenerator:
|
|||||||
%(i.name_lower))
|
%(i.name_lower))
|
||||||
if len(i.properties) > 0:
|
if len(i.properties) > 0:
|
||||||
self.c.write(' %sSkeleton *skeleton = %s%s_SKELETON (_skeleton);\n'
|
self.c.write(' %sSkeleton *skeleton = %s%s_SKELETON (_skeleton);\n'
|
||||||
|
' gboolean emit_changed = FALSE;\n'
|
||||||
|
'\n'
|
||||||
|
' g_mutex_lock (skeleton->priv->lock);\n'
|
||||||
' if (skeleton->priv->changed_properties_idle_source != NULL)\n'
|
' if (skeleton->priv->changed_properties_idle_source != NULL)\n'
|
||||||
' {\n'
|
' {\n'
|
||||||
' g_source_destroy (skeleton->priv->changed_properties_idle_source);\n'
|
' g_source_destroy (skeleton->priv->changed_properties_idle_source);\n'
|
||||||
' skeleton->priv->changed_properties_idle_source = NULL;\n'
|
' skeleton->priv->changed_properties_idle_source = NULL;\n'
|
||||||
' _%s_emit_changed (skeleton);\n'
|
' emit_changed = TRUE;\n'
|
||||||
' }\n'
|
' }\n'
|
||||||
|
' g_mutex_unlock (skeleton->priv->lock);\n'
|
||||||
|
'\n'
|
||||||
|
' if (emit_changed)\n'
|
||||||
|
' _%s_emit_changed (skeleton);\n'
|
||||||
%(i.camel_name, i.ns_upper, i.name_upper, i.name_lower))
|
%(i.camel_name, i.ns_upper, i.name_upper, i.name_lower))
|
||||||
self.c.write('}\n'
|
self.c.write('}\n'
|
||||||
'\n')
|
'\n')
|
||||||
@ -2118,24 +2126,28 @@ class CodeGenerator:
|
|||||||
self.c.write(' g_source_destroy (skeleton->priv->changed_properties_idle_source);\n')
|
self.c.write(' g_source_destroy (skeleton->priv->changed_properties_idle_source);\n')
|
||||||
self.c.write(' if (skeleton->priv->context != NULL)\n')
|
self.c.write(' if (skeleton->priv->context != NULL)\n')
|
||||||
self.c.write(' g_main_context_unref (skeleton->priv->context);\n')
|
self.c.write(' g_main_context_unref (skeleton->priv->context);\n')
|
||||||
|
self.c.write(' g_mutex_free (skeleton->priv->lock);\n')
|
||||||
self.c.write(' G_OBJECT_CLASS (%s_skeleton_parent_class)->finalize (object);\n'
|
self.c.write(' G_OBJECT_CLASS (%s_skeleton_parent_class)->finalize (object);\n'
|
||||||
'}\n'
|
'}\n'
|
||||||
'\n'%(i.name_lower))
|
'\n'%(i.name_lower))
|
||||||
|
|
||||||
# property accessors (TODO: generate PropertiesChanged signals in setter)
|
# property accessors (TODO: generate PropertiesChanged signals in setter)
|
||||||
self.c.write('static void\n'
|
|
||||||
'%s_skeleton_get_property (GObject *object,\n'
|
|
||||||
' guint prop_id,\n'
|
|
||||||
' GValue *value,\n'
|
|
||||||
' GParamSpec *pspec)\n'
|
|
||||||
'{\n'%(i.name_lower))
|
|
||||||
self.c.write(' %sSkeleton *skeleton = %s%s_SKELETON (object);\n'
|
|
||||||
' g_assert (prop_id != 0 && prop_id - 1 < %d);\n'
|
|
||||||
' g_value_copy (&skeleton->priv->properties->values[prop_id - 1], value);\n'
|
|
||||||
%(i.camel_name, i.ns_upper, i.name_upper, len(i.properties)))
|
|
||||||
self.c.write('}\n'
|
|
||||||
'\n')
|
|
||||||
if len(i.properties) > 0:
|
if len(i.properties) > 0:
|
||||||
|
self.c.write('static void\n'
|
||||||
|
'%s_skeleton_get_property (GObject *object,\n'
|
||||||
|
' guint prop_id,\n'
|
||||||
|
' GValue *value,\n'
|
||||||
|
' GParamSpec *pspec)\n'
|
||||||
|
'{\n'%(i.name_lower))
|
||||||
|
self.c.write(' %sSkeleton *skeleton = %s%s_SKELETON (object);\n'
|
||||||
|
' g_assert (prop_id != 0 && prop_id - 1 < %d);\n'
|
||||||
|
' g_mutex_lock (skeleton->priv->lock);\n'
|
||||||
|
' g_value_copy (&skeleton->priv->properties->values[prop_id - 1], value);\n'
|
||||||
|
' g_mutex_unlock (skeleton->priv->lock);\n'
|
||||||
|
%(i.camel_name, i.ns_upper, i.name_upper, len(i.properties)))
|
||||||
|
self.c.write('}\n'
|
||||||
|
'\n')
|
||||||
|
|
||||||
# if property is already scheduled then re-use entry.. though it could be
|
# if property is already scheduled then re-use entry.. though it could be
|
||||||
# that the user did
|
# that the user did
|
||||||
#
|
#
|
||||||
@ -2157,6 +2169,8 @@ class CodeGenerator:
|
|||||||
' GVariantBuilder builder;\n'
|
' GVariantBuilder builder;\n'
|
||||||
' GVariantBuilder invalidated_builder;\n'
|
' GVariantBuilder invalidated_builder;\n'
|
||||||
' guint num_changes;\n'
|
' guint num_changes;\n'
|
||||||
|
'\n'
|
||||||
|
' g_mutex_lock (skeleton->priv->lock);\n'
|
||||||
' g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));\n'
|
' g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));\n'
|
||||||
' g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));\n'
|
' g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));\n'
|
||||||
' for (l = skeleton->priv->changed_properties, num_changes = 0; l != NULL; l = l->next)\n'
|
' for (l = skeleton->priv->changed_properties, num_changes = 0; l != NULL; l = l->next)\n'
|
||||||
@ -2195,9 +2209,11 @@ class CodeGenerator:
|
|||||||
self.c.write(' g_list_free (skeleton->priv->changed_properties);\n')
|
self.c.write(' g_list_free (skeleton->priv->changed_properties);\n')
|
||||||
self.c.write(' skeleton->priv->changed_properties = NULL;\n')
|
self.c.write(' skeleton->priv->changed_properties = NULL;\n')
|
||||||
self.c.write(' skeleton->priv->changed_properties_idle_source = NULL;\n')
|
self.c.write(' skeleton->priv->changed_properties_idle_source = NULL;\n')
|
||||||
|
self.c.write(' g_mutex_unlock (skeleton->priv->lock);\n')
|
||||||
self.c.write(' return FALSE;\n'
|
self.c.write(' return FALSE;\n'
|
||||||
'}\n'
|
'}\n'
|
||||||
'\n')
|
'\n')
|
||||||
|
# holding lock while being called
|
||||||
self.c.write('static void\n'
|
self.c.write('static void\n'
|
||||||
'_%s_schedule_emit_changed (%sSkeleton *skeleton, const _ExtendedGDBusPropertyInfo *info, guint prop_id, const GValue *orig_value)\n'
|
'_%s_schedule_emit_changed (%sSkeleton *skeleton, const _ExtendedGDBusPropertyInfo *info, guint prop_id, const GValue *orig_value)\n'
|
||||||
'{\n'
|
'{\n'
|
||||||
@ -2223,7 +2239,22 @@ class CodeGenerator:
|
|||||||
' g_value_init (&cp->orig_value, G_VALUE_TYPE (orig_value));\n'
|
' g_value_init (&cp->orig_value, G_VALUE_TYPE (orig_value));\n'
|
||||||
' g_value_copy (orig_value, &cp->orig_value);\n'
|
' g_value_copy (orig_value, &cp->orig_value);\n'
|
||||||
' }\n'
|
' }\n'
|
||||||
' if (skeleton->priv->changed_properties_idle_source == NULL)\n'
|
'}\n'
|
||||||
|
'\n'
|
||||||
|
%())
|
||||||
|
|
||||||
|
# Postpone setting up the refresh source until the ::notify signal is emitted as
|
||||||
|
# this allows use of g_object_freeze_notify()/g_object_thaw_notify() ...
|
||||||
|
# This is useful when updating several properties from another thread than
|
||||||
|
# where the idle will be emitted from
|
||||||
|
self.c.write('static void\n'
|
||||||
|
'%s_skeleton_notify (GObject *object,\n'
|
||||||
|
' GParamSpec *pspec)\n'
|
||||||
|
'{\n'
|
||||||
|
' %sSkeleton *skeleton = %s%s_SKELETON (object);\n'
|
||||||
|
' g_mutex_lock (skeleton->priv->lock);\n'
|
||||||
|
' if (skeleton->priv->changed_properties != NULL &&\n'
|
||||||
|
' skeleton->priv->changed_properties_idle_source == NULL)\n'
|
||||||
' {\n'
|
' {\n'
|
||||||
' skeleton->priv->changed_properties_idle_source = g_idle_source_new ();\n'
|
' skeleton->priv->changed_properties_idle_source = g_idle_source_new ();\n'
|
||||||
' g_source_set_priority (skeleton->priv->changed_properties_idle_source, G_PRIORITY_DEFAULT);\n'
|
' g_source_set_priority (skeleton->priv->changed_properties_idle_source, G_PRIORITY_DEFAULT);\n'
|
||||||
@ -2231,18 +2262,21 @@ class CodeGenerator:
|
|||||||
' g_source_attach (skeleton->priv->changed_properties_idle_source, skeleton->priv->context);\n'
|
' g_source_attach (skeleton->priv->changed_properties_idle_source, skeleton->priv->context);\n'
|
||||||
' g_source_unref (skeleton->priv->changed_properties_idle_source);\n'
|
' g_source_unref (skeleton->priv->changed_properties_idle_source);\n'
|
||||||
' }\n'
|
' }\n'
|
||||||
|
' g_mutex_unlock (skeleton->priv->lock);\n'
|
||||||
'}\n'
|
'}\n'
|
||||||
'\n'
|
'\n'
|
||||||
%(i.name_lower))
|
%(i.name_lower, i.camel_name, i.ns_upper, i.name_upper, i.name_lower))
|
||||||
self.c.write('static void\n'
|
|
||||||
'%s_skeleton_set_property (GObject *object,\n'
|
self.c.write('static void\n'
|
||||||
' guint prop_id,\n'
|
'%s_skeleton_set_property (GObject *object,\n'
|
||||||
' const GValue *value,\n'
|
' guint prop_id,\n'
|
||||||
' GParamSpec *pspec)\n'
|
' const GValue *value,\n'
|
||||||
'{\n'%(i.name_lower))
|
' GParamSpec *pspec)\n'
|
||||||
if len(i.properties) > 0:
|
'{\n'%(i.name_lower))
|
||||||
self.c.write(' %sSkeleton *skeleton = %s%s_SKELETON (object);\n'
|
self.c.write(' %sSkeleton *skeleton = %s%s_SKELETON (object);\n'
|
||||||
' g_assert (prop_id != 0 && prop_id - 1 < %d);\n'
|
' g_assert (prop_id != 0 && prop_id - 1 < %d);\n'
|
||||||
|
' g_mutex_lock (skeleton->priv->lock);\n'
|
||||||
|
' g_object_freeze_notify (object);\n'
|
||||||
' if (!_g_value_equal (value, &skeleton->priv->properties->values[prop_id - 1]))\n'
|
' if (!_g_value_equal (value, &skeleton->priv->properties->values[prop_id - 1]))\n'
|
||||||
' {\n'
|
' {\n'
|
||||||
' if (g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (skeleton)) != NULL)\n'
|
' if (g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (skeleton)) != NULL)\n'
|
||||||
@ -2250,15 +2284,18 @@ class CodeGenerator:
|
|||||||
' g_value_copy (value, &skeleton->priv->properties->values[prop_id - 1]);\n'
|
' g_value_copy (value, &skeleton->priv->properties->values[prop_id - 1]);\n'
|
||||||
' g_object_notify_by_pspec (object, pspec);\n'
|
' g_object_notify_by_pspec (object, pspec);\n'
|
||||||
' }\n'
|
' }\n'
|
||||||
|
' g_mutex_unlock (skeleton->priv->lock);\n'
|
||||||
|
' g_object_thaw_notify (object);\n'
|
||||||
%(i.camel_name, i.ns_upper, i.name_upper, len(i.properties), i.name_lower, i.name_lower))
|
%(i.camel_name, i.ns_upper, i.name_upper, len(i.properties), i.name_lower, i.name_lower))
|
||||||
self.c.write('}\n'
|
self.c.write('}\n'
|
||||||
'\n')
|
'\n')
|
||||||
|
|
||||||
self.c.write('static void\n'
|
self.c.write('static void\n'
|
||||||
'%s_skeleton_init (%sSkeleton *skeleton)\n'
|
'%s_skeleton_init (%sSkeleton *skeleton)\n'
|
||||||
'{\n'
|
'{\n'
|
||||||
' skeleton->priv = G_TYPE_INSTANCE_GET_PRIVATE (skeleton, %sTYPE_%s_SKELETON, %sSkeletonPrivate);\n'
|
' skeleton->priv = G_TYPE_INSTANCE_GET_PRIVATE (skeleton, %sTYPE_%s_SKELETON, %sSkeletonPrivate);\n'
|
||||||
%(i.name_lower, i.camel_name, i.ns_upper, i.name_upper, i.camel_name))
|
%(i.name_lower, i.camel_name, i.ns_upper, i.name_upper, i.camel_name))
|
||||||
|
self.c.write(' skeleton->priv->lock = g_mutex_new ();\n')
|
||||||
self.c.write(' skeleton->priv->context = g_main_context_get_thread_default ();\n')
|
self.c.write(' skeleton->priv->context = g_main_context_get_thread_default ();\n')
|
||||||
self.c.write(' if (skeleton->priv->context != NULL)\n')
|
self.c.write(' if (skeleton->priv->context != NULL)\n')
|
||||||
self.c.write(' g_main_context_ref (skeleton->priv->context);\n')
|
self.c.write(' g_main_context_ref (skeleton->priv->context);\n')
|
||||||
@ -2281,10 +2318,12 @@ class CodeGenerator:
|
|||||||
'\n'
|
'\n'
|
||||||
' gobject_class = G_OBJECT_CLASS (klass);\n'
|
' gobject_class = G_OBJECT_CLASS (klass);\n'
|
||||||
' gobject_class->finalize = %s_skeleton_finalize;\n'
|
' gobject_class->finalize = %s_skeleton_finalize;\n'
|
||||||
' gobject_class->get_property = %s_skeleton_get_property;\n'
|
%(i.name_lower, i.camel_name, i.camel_name, i.name_lower))
|
||||||
' gobject_class->set_property = %s_skeleton_set_property;\n'
|
|
||||||
'\n'%(i.name_lower, i.camel_name, i.camel_name, i.name_lower, i.name_lower, i.name_lower))
|
|
||||||
if len(i.properties) > 0:
|
if len(i.properties) > 0:
|
||||||
|
self.c.write(' gobject_class->get_property = %s_skeleton_get_property;\n'
|
||||||
|
' gobject_class->set_property = %s_skeleton_set_property;\n'
|
||||||
|
' gobject_class->notify = %s_skeleton_notify;\n'
|
||||||
|
'\n'%(i.name_lower, i.name_lower, i.name_lower))
|
||||||
self.c.write('\n'
|
self.c.write('\n'
|
||||||
' %s_override_properties (gobject_class, 1);\n'%(i.name_lower))
|
' %s_override_properties (gobject_class, 1);\n'%(i.name_lower))
|
||||||
self.c.write('\n'
|
self.c.write('\n'
|
||||||
|
Loading…
Reference in New Issue
Block a user