Add g_source_set_dispose_function() for setting a dispose function for GSource

This allows GSource implementors to safely clear any other references to
the GSource while the GSource is still valid, unlike when doing the same
from the finalize function.

After the dispose function has run, it is valid for the reference count
of the GSource to be > 0 again to allow the case where another thread in
the mean-time got access to the GSource reference before the dispose
function was called.

This allows fixing a thread-safety issue in the GCancellable, GstBus and
various other GSource implementations.
This commit is contained in:
Sebastian Dröge 2019-10-20 11:10:21 +03:00
parent 691485fb7c
commit 0adf5cae35
2 changed files with 78 additions and 0 deletions

View File

@ -361,6 +361,8 @@ struct _GSourcePrivate
* let it remain empty on Windows) to avoid #ifdef all over the place.
*/
GSList *fds;
GSourceDisposeFunc dispose;
};
typedef struct _GSourceIter
@ -926,6 +928,39 @@ g_source_new (GSourceFuncs *source_funcs,
return source;
}
/**
* g_source_set_dispose_function:
* @source: A #GSource to set the dispose function on
* @dispose: #GSourceDisposeFunc to set on the source
*
* Set @dispose as dispose function on @source. @dispose will be called once
* the reference count of @source reaches 0 but before any of the state of the
* source is freed, especially before the finalize function is called.
*
* This means that at this point @source is still a valid #GSource and it is
* allow for the reference count to increase again until @dispose returns.
*
* The dispose function can be used to clear any "weak" references to the
* @source in other data structures in a thread-safe way where it is possible
* for another thread to increase the reference count of @source again while
* it is being freed.
*
* The finalize function can not be used for this purpose as at that point
* @source is already partially freed and not valid anymore.
*
* This should only ever be called from #GSource implementations.
*
* Since: 2.64
**/
void
g_source_set_dispose_function (GSource *source,
GSourceDisposeFunc dispose)
{
g_return_if_fail (source != NULL);
g_return_if_fail (source->priv->dispose == NULL);
source->priv->dispose = dispose;
}
/* Holds context's lock */
static void
g_source_iter_init (GSourceIter *iter,
@ -2094,6 +2129,28 @@ g_source_unref_internal (GSource *source,
if (g_atomic_int_dec_and_test (&source->ref_count))
{
/* 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;
}
}
TRACE (GLIB_SOURCE_BEFORE_FREE (source, context,
source->source_funcs->finalize));

View File

@ -204,6 +204,20 @@ typedef gboolean (*GSourceFunc) (gpointer user_data);
typedef void (*GChildWatchFunc) (GPid pid,
gint status,
gpointer user_data);
/**
* GSourceDisposeFunc:
* @source: #GSource that is currently being disposed
*
* Dispose function for @source. See g_source_set_dispose_function() for
* details.
*
* Since: 2.64
*/
GLIB_AVAILABLE_TYPE_IN_2_64
typedef void (*GSourceDisposeFunc) (GSource *source);
struct _GSource
{
/*< private >*/
@ -536,6 +550,13 @@ GMainContext *g_main_loop_get_context (GMainLoop *loop);
GLIB_AVAILABLE_IN_ALL
GSource *g_source_new (GSourceFuncs *source_funcs,
guint struct_size);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
GLIB_AVAILABLE_IN_2_64
void g_source_set_dispose_function (GSource *source,
GSourceDisposeFunc dispose);
G_GNUC_END_IGNORE_DEPRECATIONS
GLIB_AVAILABLE_IN_ALL
GSource *g_source_ref (GSource *source);
GLIB_AVAILABLE_IN_ALL