diff --git a/gio/gcancellable.c b/gio/gcancellable.c index 3205820be..c9d0511cd 100644 --- a/gio/gcancellable.c +++ b/gio/gcancellable.c @@ -566,6 +566,16 @@ g_cancellable_connect (GCancellable *cancellable, g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), 0); + /* If the cancellable is already cancelled we may end up calling the callback + * immediately, and the callback may unref the Cancellable, so we need to add + * an extra reference here. We can't do it only in the case the cancellable + * is already cancelled because it can be potentially be reset, so we can't + * rely on the atomic value only, but we need to be locked to be really sure. + * At the same time we don't want to wake up the ToggleNotify if toggle + * references are enabled while we're locked. + */ + g_object_ref (cancellable); + g_mutex_lock (&cancellable->priv->mutex); if (g_atomic_int_get (&cancellable->priv->cancelled)) @@ -593,6 +603,8 @@ g_cancellable_connect (GCancellable *cancellable, g_mutex_unlock (&cancellable->priv->mutex); + g_object_unref (cancellable); + return id; } diff --git a/gio/tests/cancellable.c b/gio/tests/cancellable.c index 5fcdf2ff7..eb45f3564 100644 --- a/gio/tests/cancellable.c +++ b/gio/tests/cancellable.c @@ -909,6 +909,25 @@ test_connect_to_disposing_callback (void) g_assert_null (cancellable); } +static void +test_connect_cancelled_to_disposing_callback (void) +{ + GCancellable *cancellable; + gulong id; + + g_test_summary ("A cancellable signal callback can unref the cancellable"); + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3643"); + + cancellable = g_cancellable_new (); + g_object_add_weak_pointer (G_OBJECT (cancellable), (gpointer *) &cancellable); + + g_cancellable_cancel (cancellable); + id = g_cancellable_connect (cancellable, G_CALLBACK (assert_references_and_unref), + GINT_TO_POINTER (2), NULL); + g_assert_cmpuint (id, ==, 0); + g_assert_null (cancellable); +} + int main (int argc, char *argv[]) { @@ -917,6 +936,7 @@ main (int argc, char *argv[]) g_test_add_func ("/cancellable/multiple-concurrent", test_cancel_multiple_concurrent); g_test_add_func ("/cancellable/null", test_cancel_null); g_test_add_func ("/cancellable/connect-to-disposing-callback", test_connect_to_disposing_callback); + g_test_add_func ("/cancellable/connect-cancelled-to-disposing-callback", test_connect_cancelled_to_disposing_callback); g_test_add_func ("/cancellable/disconnect-on-cancelled-callback-hangs", test_cancellable_disconnect_on_cancelled_callback_hangs); g_test_add_func ("/cancellable/resets-on-cancel-callback-hangs", test_cancellable_reset_on_cancelled_callback_hangs); g_test_add_func ("/cancellable/poll-fd", test_cancellable_poll_fd);