mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-08-02 07:23:41 +02:00
gcancellable: Fix minor race between GCancellable and GCancellableSource
There’s a minor race condition between cancellation of a `GCancellable`, and disposal/finalisation of a `GCancellableSource` in another thread. Thread A Thread B g_cancellable_cancel(C) →cancellable_source_cancelled(C, S) g_source_unref(S) cancellable_source_dispose(S) →→g_source_ref(S) →→# S is invalid at this point; crash Thankfully, the `GCancellable` sets `cancelled_running` while it’s emitting the `cancelled` signal, so if `cancellable_source_dispose()` is called while that’s high, we know that the thread which is doing the cancellation has already started (or is committed to starting) calling `cancellable_source_cancelled()`. Fix the race by resurrecting the `GCancellableSource` in `cancellable_source_dispose()`, and signalling this using `GCancellableSource.resurrected_during_cancellation`. Check for that flag in `cancellable_source_cancelled()` and ignore cancellation if it’s set. The modifications to `resurrected_during_cancellation` and the cancellable source’s refcount have to be done with `cancellable_mutex` held so that they are seen atomically by each thread. This should not affect performance too much, as it only happens during cancellation or disposal of a `GCancellableSource`. Signed-off-by: Philip Withnall <withnall@endlessm.com> Fixes: #1841
This commit is contained in:
@@ -643,6 +643,8 @@ typedef struct {
|
||||
|
||||
GCancellable *cancellable;
|
||||
gulong cancelled_handler;
|
||||
/* Protected by cancellable_mutex: */
|
||||
gboolean resurrected_during_cancellation;
|
||||
} GCancellableSource;
|
||||
|
||||
/*
|
||||
@@ -661,8 +663,24 @@ cancellable_source_cancelled (GCancellable *cancellable,
|
||||
gpointer user_data)
|
||||
{
|
||||
GSource *source = user_data;
|
||||
GCancellableSource *cancellable_source = (GCancellableSource *) source;
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
|
||||
/* Drop the reference added in cancellable_source_dispose(); see the comment there.
|
||||
* The reference must be dropped after unlocking @cancellable_mutex since
|
||||
* it could be the final reference, and the dispose function takes
|
||||
* @cancellable_mutex. */
|
||||
if (cancellable_source->resurrected_during_cancellation)
|
||||
{
|
||||
cancellable_source->resurrected_during_cancellation = FALSE;
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
g_source_unref (source);
|
||||
return;
|
||||
}
|
||||
|
||||
g_source_ref (source);
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
g_source_set_ready_time (source, 0);
|
||||
g_source_unref (source);
|
||||
}
|
||||
@@ -684,12 +702,37 @@ cancellable_source_dispose (GSource *source)
|
||||
{
|
||||
GCancellableSource *cancellable_source = (GCancellableSource *)source;
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
|
||||
if (cancellable_source->cancellable)
|
||||
{
|
||||
if (cancellable_source->cancellable->priv->cancelled_running)
|
||||
{
|
||||
/* There can be a race here: if thread A has called
|
||||
* g_cancellable_cancel() and has got as far as committing to call
|
||||
* cancellable_source_cancelled(), then thread B drops the final
|
||||
* ref on the GCancellableSource before g_source_ref() is called in
|
||||
* cancellable_source_cancelled(), then cancellable_source_dispose()
|
||||
* will run through and the GCancellableSource will be finalised
|
||||
* before cancellable_source_cancelled() gets to g_source_ref(). It
|
||||
* will then be left in a state where it’s committed to using a
|
||||
* dangling GCancellableSource pointer.
|
||||
*
|
||||
* Eliminate that race by resurrecting the #GSource temporarily, and
|
||||
* then dropping that reference in cancellable_source_cancelled(),
|
||||
* which should be guaranteed to fire because we’re inside a
|
||||
* @cancelled_running block.
|
||||
*/
|
||||
g_source_ref (source);
|
||||
cancellable_source->resurrected_during_cancellation = TRUE;
|
||||
}
|
||||
|
||||
g_clear_signal_handler (&cancellable_source->cancelled_handler,
|
||||
cancellable_source->cancellable);
|
||||
g_clear_object (&cancellable_source->cancellable);
|
||||
}
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
|
Reference in New Issue
Block a user