mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-02-22 18:22:11 +01:00
Merge branch 'wip/3v1n0/gmain-source-reffing-fixes' into 'main'
glib/gmain: Avoid potential dispose race on GSource due to refcount dance Closes #3612 See merge request GNOME/glib!4501
This commit is contained in:
commit
30fbe0e859
59
glib/gmain.c
59
glib/gmain.c
@ -2191,12 +2191,13 @@ g_source_set_name_by_id (guint tag,
|
||||
GSource *
|
||||
g_source_ref (GSource *source)
|
||||
{
|
||||
int old_ref G_GNUC_UNUSED;
|
||||
g_return_val_if_fail (source != NULL, NULL);
|
||||
|
||||
old_ref = g_atomic_int_add (&source->ref_count, 1);
|
||||
/* We allow ref_count == 0 here to allow the dispose function to resurrect
|
||||
* the GSource if needed */
|
||||
g_return_val_if_fail (g_atomic_int_get (&source->ref_count) >= 0, NULL);
|
||||
|
||||
g_atomic_int_inc (&source->ref_count);
|
||||
g_return_val_if_fail (old_ref >= 0, NULL);
|
||||
|
||||
return source;
|
||||
}
|
||||
@ -2210,34 +2211,60 @@ g_source_unref_internal (GSource *source,
|
||||
{
|
||||
gpointer old_cb_data = NULL;
|
||||
GSourceCallbackFuncs *old_cb_funcs = NULL;
|
||||
int old_ref;
|
||||
|
||||
g_return_if_fail (source != NULL);
|
||||
|
||||
old_ref = g_atomic_int_get (&source->ref_count);
|
||||
|
||||
retry_beginning:
|
||||
if (old_ref > 1)
|
||||
{
|
||||
/* We have many references. If we can decrement the ref counter, we are done. */
|
||||
if (!g_atomic_int_compare_and_exchange_full ((int *) &source->ref_count,
|
||||
old_ref, old_ref - 1,
|
||||
&old_ref))
|
||||
goto retry_beginning;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
g_return_if_fail (old_ref > 0);
|
||||
|
||||
if (!have_lock && context)
|
||||
LOCK_CONTEXT (context);
|
||||
|
||||
if (g_atomic_int_dec_and_test (&source->ref_count))
|
||||
/* We are about to drop the last reference, there's not guarantee at this
|
||||
* point that another thread already changed the value at this point or
|
||||
* that is also entering the disposal phase, but there is no much we can do
|
||||
* and dropping the reference too early would be still risky since it could
|
||||
* lead to a preventive finalization.
|
||||
* So let's just get all the threads that reached this point to get in, while
|
||||
* the final check on whether is the case or not to continue with the
|
||||
* finalization will be done by a final unique atomic dec and test.
|
||||
*/
|
||||
if (old_ref == 1)
|
||||
{
|
||||
/* If there's a dispose function, call this first */
|
||||
if (source->priv->dispose)
|
||||
{
|
||||
/* Temporarily increase the ref count again so that GSource methods
|
||||
* can be called from dispose(). */
|
||||
g_atomic_int_inc (&source->ref_count);
|
||||
if (context)
|
||||
UNLOCK_CONTEXT (context);
|
||||
source->priv->dispose (source);
|
||||
if (context)
|
||||
LOCK_CONTEXT (context);
|
||||
}
|
||||
|
||||
/* Now the reference count might be bigger than 0 again, in which
|
||||
* case we simply return from here before freeing the source */
|
||||
if (!g_atomic_int_dec_and_test (&source->ref_count))
|
||||
{
|
||||
if (!have_lock && context)
|
||||
UNLOCK_CONTEXT (context);
|
||||
return;
|
||||
}
|
||||
/* At this point the source can have been revived by any of the threads
|
||||
* acting on it or it's really ready for being finalized.
|
||||
*/
|
||||
if (!g_atomic_int_compare_and_exchange_full ((int *) &source->ref_count,
|
||||
1, 0, &old_ref))
|
||||
{
|
||||
if (!have_lock && context)
|
||||
UNLOCK_CONTEXT (context);
|
||||
|
||||
goto retry_beginning;
|
||||
}
|
||||
|
||||
TRACE (GLIB_SOURCE_BEFORE_FREE (source, context,
|
||||
@ -2334,7 +2361,7 @@ void
|
||||
g_source_unref (GSource *source)
|
||||
{
|
||||
g_return_if_fail (source != NULL);
|
||||
g_return_if_fail (g_atomic_int_get (&source->ref_count) > 0);
|
||||
/* refcount is checked inside g_source_unref_internal() */
|
||||
|
||||
g_source_unref_internal (source, source->context, FALSE);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user