diff --git a/gobject/gobject.c b/gobject/gobject.c index 64557eaa02fa126a87ebc90843aae1ef13037f86..ae0fe928c8e4385a603bd84141f26a17b669e4b6 100644 --- a/gobject/gobject.c +++ b/gobject/gobject.c @@ -191,6 +191,11 @@ G_STATIC_ASSERT(sizeof(GObject) == sizeof(GObjectReal)); G_STATIC_ASSERT(G_STRUCT_OFFSET(GObject, ref_count) == G_STRUCT_OFFSET(GObjectReal, ref_count)); G_STATIC_ASSERT(G_STRUCT_OFFSET(GObject, qdata) == G_STRUCT_OFFSET(GObjectReal, qdata)); +typedef struct +{ + GObject *object; + guint ref_level; +} GMutedRef; /* --- prototypes --- */ static void g_object_base_class_init (GObjectClass *class); @@ -237,6 +242,10 @@ static void object_interface_check_properties (gpointer check_d gpointer g_iface); static void weak_locations_free_unlocked (GSList **weak_locations); +static void g_muted_ref_init (GMutedRef *muted_ref, + GObject *object); +static void g_muted_ref_release (GMutedRef *muted_ref); + /* --- typedefs --- */ typedef struct _GObjectNotifyQueue GObjectNotifyQueue; @@ -1364,13 +1373,14 @@ g_object_notify (GObject *object, const gchar *property_name) { GParamSpec *pspec; - + GMutedRef ref; + g_return_if_fail (G_IS_OBJECT (object)); g_return_if_fail (property_name != NULL); if (g_atomic_int_get (&object->ref_count) == 0) return; - - g_object_ref (object); + + g_muted_ref_init (&ref, object); /* We don't need to get the redirect target * (by, e.g. calling g_object_class_find_property()) * because g_object_notify_queue_add() does that @@ -1387,7 +1397,7 @@ g_object_notify (GObject *object, property_name); else g_object_notify_by_spec_internal (object, pspec); - g_object_unref (object); + g_muted_ref_release (&ref); } /** @@ -1440,6 +1450,7 @@ void g_object_notify_by_pspec (GObject *object, GParamSpec *pspec) { + GMutedRef ref; g_return_if_fail (G_IS_OBJECT (object)); g_return_if_fail (G_IS_PARAM_SPEC (pspec)); @@ -1447,9 +1458,9 @@ g_object_notify_by_pspec (GObject *object, if (g_atomic_int_get (&object->ref_count) == 0) return; - g_object_ref (object); + g_muted_ref_init (&ref, object); g_object_notify_by_spec_internal (object, pspec); - g_object_unref (object); + g_muted_ref_release (&ref); } /** @@ -3334,6 +3345,7 @@ g_object_force_floating (GObject *object) typedef struct { GObject *object; guint n_toggle_refs; + guint mute_count; struct { GToggleNotify notify; gpointer data; @@ -3365,7 +3377,106 @@ toggle_refs_notify (GObject *object, * will only be notified when there is exactly one of them. */ g_assert (tstack.n_toggle_refs == 1); - tstack.toggle_refs[0].notify (tstack.toggle_refs[0].data, tstack.object, is_last_ref); + + if (tstack.mute_count == 0) + tstack.toggle_refs[0].notify (tstack.toggle_refs[0].data, + tstack.object, + is_last_ref); +} + +/** + * g_muted_ref_init: + * @muted_ref: Address of an uninitialized #GMutedRef. + * @object: The object to reference. + * + * Increases the reference count on @object like g_object_ref(), but also + * mutes any toggle reference notifications that might have otherwise + * occurred between a g_object_ref() and g_object_unref(). + * + * This must be followed by a call to g_muted_ref_release(). + */ +static void +g_muted_ref_init (GMutedRef *muted_ref, + GObject *object) +{ + guint i; + + g_assert (muted_ref != NULL); + + muted_ref->object = NULL; + muted_ref->ref_level = 0; + + g_return_if_fail (G_IS_OBJECT (object)); + + muted_ref->object = object; + muted_ref->ref_level = 1; + + if (OBJECT_HAS_TOGGLE_REF (object)) + { + ToggleRefStack *tstack; + + G_LOCK (toggle_refs_mutex); + tstack = g_datalist_id_get_data (&object->qdata, quark_toggle_refs); + if (tstack) + { + tstack->mute_count++; + muted_ref->ref_level++; + } + G_UNLOCK (toggle_refs_mutex); + } + + /* If there are toggle refs then we need to increase the ref_count by at + * least 2 to ensure it stays >= 2 for the lifetime of the muted reference. + * That way no toggles can occur and therefore we can't get any mismatch of + * toggles up vs down. + */ + for (i = 0; i < muted_ref->ref_level; i++) + g_object_ref (object); +} + +/** + * g_muted_ref_release: + * @muted_ref: A #GMutedRef that was initialized by g_muted_ref_init(). + * + * Releases a muted reference, decreasing the reference count on the associated + * object by the same amount as g_muted_ref_init() increased it. + * + * After all muted references are removed from an object, toggle notifications + * may occur again. + */ +static void +g_muted_ref_release (GMutedRef *muted_ref) +{ + guint i; + + g_return_if_fail (muted_ref != NULL); + g_return_if_fail (G_IS_OBJECT (muted_ref->object)); + g_return_if_fail (muted_ref->ref_level >= 1); + g_return_if_fail (muted_ref->ref_level <= 2); + + for (i = 0; i < muted_ref->ref_level; i++) + g_object_unref (muted_ref->object); + + /* Toggle refs might have been added after the call to g_muted_ref_init + * so we need to be careful to only do this if ref_level == 2. + */ + if (muted_ref->ref_level == 2 && OBJECT_HAS_TOGGLE_REF (muted_ref->object)) + { + ToggleRefStack *tstack; + + G_LOCK (toggle_refs_mutex); + tstack = g_datalist_id_get_data (&muted_ref->object->qdata, + quark_toggle_refs); + if (tstack) + { + g_assert (tstack->mute_count > 0); + tstack->mute_count--; + } + G_UNLOCK (toggle_refs_mutex); + } + + muted_ref->object = NULL; + muted_ref->ref_level = 0; } /** @@ -3435,6 +3546,7 @@ g_object_add_toggle_ref (GObject *object, tstack = g_renew (ToggleRefStack, NULL, 1); tstack->object = object; tstack->n_toggle_refs = 1; + tstack->mute_count = 0; i = 0; } diff --git a/tests/gobject/references.c b/tests/gobject/references.c index 36ff35c63838c57129415d730fbc6139d8e1cd51..b70bb25d4011676c91255401763a3c531f2f5d16 100644 --- a/tests/gobject/references.c +++ b/tests/gobject/references.c @@ -50,6 +50,7 @@ typedef struct _TestObjectClass TestObjectClass; struct _TestObject { GObject parent_instance; + gint something; }; struct _TestObjectClass { @@ -58,6 +59,51 @@ struct _TestObjectClass G_DEFINE_TYPE (TestObject, test_object, G_TYPE_OBJECT) +enum +{ + PROP_SOMETHING = 1 +}; + +static GParamSpec *something_spec = NULL; + +static void +test_object_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TestObject *test_object = (TestObject *) object; + + switch (property_id) + { + case PROP_SOMETHING: + test_object->something = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +test_object_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TestObject *test_object = (TestObject *) object; + + switch (property_id) + { + case PROP_SOMETHING: + g_value_set_int (value, test_object->something); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + static void test_object_finalize (GObject *object) { @@ -72,6 +118,18 @@ test_object_class_init (TestObjectClass *class) GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->finalize = test_object_finalize; + + something_spec = + g_param_spec_int ("something", "Something", "Some thing", + G_MININT, G_MAXINT, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + object_class->set_property = test_object_set_property; + object_class->get_property = test_object_get_property; + g_object_class_install_property (object_class, + PROP_SOMETHING, + something_spec); } static void @@ -255,6 +313,15 @@ main (int argc, g_assert (toggle_ref2_strengthened == FALSE); g_assert (object_destroyed == FALSE); + /* Test that g_object_notify does NOT toggle references internally */ + clear_flags (); + g_object_notify (object, "something"); + g_assert_false (toggle_ref1_weakened); + g_assert_false (toggle_ref1_strengthened); + g_assert_false (toggle_ref2_weakened); + g_assert_false (toggle_ref2_strengthened); + g_assert_false (object_destroyed); + clear_flags (); /* Check that removing a toggle ref with %NULL data works fine. */ g_object_remove_toggle_ref (object, toggle_ref2, NULL);