mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-10 03:16:17 +01:00
gobject: fix race in toggle ref during g_object_ref()
Previously: 1. old_val = atomic_add(&object->ref_count); 2. if (old_val == 1 && OBJECT_HAS_TOGGLE_REF (object)) { toggle_notify() } As old_val was 1, you might think that no other thread can have a valid reference to object. However, that's not the case. For one, GWeakRef can be used to create another strong reference. More easily, the single reference can be shared between multiple threads (as long as the code takes care that the object lives long enough). That means, another thread can easily add and drop references (including toggle references). All between step 1 and 2. A race here might be hard to hit, and the effect might not be obviously bad. However, consider old_val is 1 due to a normal reference, and another thread adds a toggle ref between step 1. and 2. Then we would notify a toggle from 1->2, although a newly added toggle ref is expected to always start with a normal and a toggle reference. The first toggle notification is expected to notify about the loss of other references, not about getting a second reference. To handle this properly, when we increase the reference count from 1 to 2, we must do so under a lock and check for the toggle notification. As we now correctly track the toggle behavior, we can also assert in toggle_refs_get_notify_unlocked() that n_toggle_refs agrees with the number of references, that is, that the user did always match g_object_add_toggle_ref() with g_object_remove_toggle_ref(). The downside is here too, that there is now a case (when increasing the reference count from 1 to 2) where we need to take the global lock. That performance problem should be addresses by using per-object locks instead of a global lock.
This commit is contained in:
parent
408dc69186
commit
9ae43169cf
@ -3587,12 +3587,7 @@ toggle_refs_get_notify_unlocked (GObject *object,
|
||||
|
||||
if (tstackptr->n_toggle_refs != 1)
|
||||
{
|
||||
/* There are multiple references. We won't notify.
|
||||
*
|
||||
* Note that the user MUST pair g_object_add_toggle_ref() with
|
||||
* g_object_remove_toggle_ref(). In that case, having a
|
||||
* "n_toggle_refs" larger than one, also means that we have multiple
|
||||
* references. */
|
||||
g_critical ("Unexpected number of toggle-refs. g_object_add_toggle_ref() must be paired with g_object_remove_toggle_ref()");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -3600,21 +3595,6 @@ toggle_refs_get_notify_unlocked (GObject *object,
|
||||
return tstackptr->toggle_refs[0].notify;
|
||||
}
|
||||
|
||||
static void
|
||||
toggle_refs_notify (GObject *object,
|
||||
gboolean is_last_ref)
|
||||
{
|
||||
GToggleNotify notify;
|
||||
gpointer data;
|
||||
|
||||
G_LOCK (toggle_refs_mutex);
|
||||
notify = toggle_refs_get_notify_unlocked (object, &data);
|
||||
G_UNLOCK (toggle_refs_mutex);
|
||||
|
||||
if (notify)
|
||||
notify (data, object, is_last_ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_object_add_toggle_ref: (skip)
|
||||
* @object: a #GObject
|
||||
@ -3781,19 +3761,49 @@ gpointer
|
||||
(g_object_ref) (gpointer _object)
|
||||
{
|
||||
GObject *object = _object;
|
||||
gint old_val;
|
||||
gboolean object_already_finalized;
|
||||
GToggleNotify toggle_notify;
|
||||
gpointer toggle_data;
|
||||
gint old_ref;
|
||||
|
||||
g_return_val_if_fail (G_IS_OBJECT (object), NULL);
|
||||
|
||||
old_val = g_atomic_int_add (&object->ref_count, 1);
|
||||
object_already_finalized = (old_val <= 0);
|
||||
old_ref = g_atomic_int_get (&object->ref_count);
|
||||
|
||||
retry:
|
||||
toggle_notify = NULL;
|
||||
if (old_ref > 1 && old_ref < G_MAXINT)
|
||||
{
|
||||
/* Fast-path. We have apparently more than 1 references already. No
|
||||
* special handling for toggle references, just increment the ref count. */
|
||||
if (!g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
|
||||
old_ref, old_ref + 1, &old_ref))
|
||||
goto retry;
|
||||
}
|
||||
else if (old_ref == 1)
|
||||
{
|
||||
gboolean do_retry;
|
||||
|
||||
/* With ref count 1, check whether we need to emit a toggle notification. */
|
||||
G_LOCK (toggle_refs_mutex);
|
||||
toggle_notify = toggle_refs_get_notify_unlocked (object, &toggle_data);
|
||||
do_retry = !g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
|
||||
old_ref, old_ref + 1, &old_ref);
|
||||
G_UNLOCK (toggle_refs_mutex);
|
||||
if (do_retry)
|
||||
goto retry;
|
||||
}
|
||||
else
|
||||
{
|
||||
gboolean object_already_finalized = TRUE;
|
||||
|
||||
g_return_val_if_fail (!object_already_finalized, NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (old_val == 1 && OBJECT_HAS_TOGGLE_REF (object))
|
||||
toggle_refs_notify (object, FALSE);
|
||||
TRACE (GOBJECT_OBJECT_REF (object, G_TYPE_FROM_INSTANCE (object), old_ref));
|
||||
|
||||
TRACE (GOBJECT_OBJECT_REF (object, G_TYPE_FROM_INSTANCE (object), old_val));
|
||||
if (toggle_notify)
|
||||
toggle_notify (toggle_data, object, FALSE);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user