From c62001ec82851cbb0f71cc2946592519113b0a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 13 Jun 2022 18:48:02 +0200 Subject: [PATCH] gio: Add GCancellableChild, a GCancellable with a GCancellable parent Many times we need to manage multiple async operations inside an object and each operation may need to be controlled via a different GCancellable, however this implies keeping track of all of them. It's instead at times convenient to just create a main object cancellable to control a chain of GCancellable's that depend on it. As per this introduce a GCancellable subtype that requires a parent GCancellable object that is monitored for cancellation. As per the way GCancellable's "cancelled" signal is defined we need to disconnect on it outside the actual callback and we use an idle for that. We avoid keeping references in the involved objects so in case the idle functions may just do nothing. --- gio/gcancellablechild.c | 338 +++++++++++++++++++++++++++++ gio/gcancellablechild.h | 57 +++++ gio/gio-autocleanups.h | 1 + gio/gio.h | 1 + gio/meson.build | 2 + gio/tests/autoptr.c | 2 + gio/tests/cancellable.c | 449 +++++++++++++++++++++++++++++++++++++++ gio/tests/defaultvalue.c | 1 + 8 files changed, 851 insertions(+) create mode 100644 gio/gcancellablechild.c create mode 100644 gio/gcancellablechild.h diff --git a/gio/gcancellablechild.c b/gio/gcancellablechild.c new file mode 100644 index 000000000..c7d673d2c --- /dev/null +++ b/gio/gcancellablechild.c @@ -0,0 +1,338 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2022 Canonical Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: Marco Trevisan + */ + +#include "config.h" + +#include "gcancellablechild.h" + +#include "glibintl.h" + +/** + * SECTION:gcancellablechild + * @short_description: Thread-safe Operation Cancellation Stack + * @include: gio/gio.h + * + * GCancellableChild is an implementation of #GCancellable that allows to + * monitor a parent cancellable to allow cancellation of synchronous and + * asynchronous operations when the parent cancellable gets cancelled. + * + * This allows to safely create a chain of cancellable's that are depending + * on a main #GCancellable cancellation. + * + * So for instance an object may hold a global cancellable to be cancelled + * when the object gets cancelled, while creating new child cancellable's + * for each single operation it needs to do that are depending on the + * main #GCancellable. + */ + +typedef struct +{ + GCancellableClass parent_class; +} GCancellableChildClass; + +typedef struct { + GCancellableChild *self; /* atomic */ + gulong id; +} ConnectionData; + +struct _GCancellableChild +{ + GCancellable parent_instance; + + GCancellable *parent_cancellable; + ConnectionData *connection_data; +}; + +enum +{ + PROP_0, + PROP_PARENT, +}; + +G_DEFINE_FINAL_TYPE (GCancellableChild, g_cancellable_child, G_TYPE_CANCELLABLE) + +static void +connection_data_free (ConnectionData *data) +{ + g_atomic_pointer_set (&data->self, NULL); + g_free (data); +} + +typedef struct { + GWeakRef cancellable_ref; + gulong connection_id; +} IdleData; + +static void +idle_data_free (IdleData *data) +{ + g_weak_ref_clear (&data->cancellable_ref); + g_free (data); +} + +static gboolean +on_parent_cancellable_cancel_idle (IdleData *idle_data) +{ + GCancellable *cancellable = g_weak_ref_get (&idle_data->cancellable_ref); + + if (cancellable) + { + g_cancellable_disconnect (cancellable, idle_data->connection_id); + g_object_unref (cancellable); + } + + return G_SOURCE_REMOVE; +} + +static void +disconnect_from_parent_idle (GCancellable *parent, + gulong connection_id) +{ + IdleData *idle_data = g_new0 (IdleData, 1); + GSource *source = g_idle_source_new (); + + g_weak_ref_init (&idle_data->cancellable_ref, parent); + idle_data->connection_id = connection_id; + + g_source_set_priority (source, G_PRIORITY_HIGH_IDLE); + g_source_set_callback (source, + G_SOURCE_FUNC (on_parent_cancellable_cancel_idle), + idle_data, + (GDestroyNotify) idle_data_free); + g_source_set_static_name (source, "[gio] cancellable_child_disconnect_idle"); + + g_source_attach (source, g_main_context_get_thread_default ()); + g_source_unref (source); +} + +static void +on_parent_cancellable_cancelled (GCancellable *parent_cancellable, + gpointer data) +{ + ConnectionData *connection_data = data; + GCancellableChild *self = NULL; + + /* Let's avoid that any other thread may dispose us while processing this */ + g_set_object (&self, g_atomic_pointer_exchange (&connection_data->self, NULL)); + + if (self && + g_atomic_pointer_compare_and_exchange (&self->connection_data, + connection_data, NULL)) + { + /* Cancellable may have been got cancelled in another thread, so we need + * to ensure that we act once the cancellable has been released. + */ + disconnect_from_parent_idle (parent_cancellable, connection_data->id); + + g_cancellable_cancel (G_CANCELLABLE (self)); + } + + g_clear_object (&self); +} + +/** + * g_cancellable_child_new: + * @parent: the parent #GCancellable to use + * + * Creates a new #GCancellableChild object with a @parent as + * parent cancellable that will be monitored for cancellation. + * + * If @parent is already cancelled, the newly created cancellable will + * be cancelled immediately. + * + * Returns: a #GCancellableChild. + * + * Since: 2.76 + */ +GCancellableChild * +g_cancellable_child_new (GCancellable *parent) +{ + g_return_val_if_fail (G_IS_CANCELLABLE (parent), NULL); + + return g_object_new (G_TYPE_CANCELLABLE_CHILD, + "parent", parent, + NULL); +} + +/** + * g_cancellable_child_get_parent: + * @cancellable_child: the parent #GCancellable to use + * + * Gets the parent @cancellable_child's #GCancellable + * + * Returns: (transfer none): @cancellable_child's parent #GCancellable + * + * Since: 2.76 + */ +GCancellable * +g_cancellable_child_get_parent (GCancellableChild *cancellable_child) +{ + g_return_val_if_fail (G_IS_CANCELLABLE_CHILD (cancellable_child), NULL); + + return cancellable_child->parent_cancellable; +} + +static void +g_cancellable_child_cancel (GCancellable *cancellable) +{ + GCancellableChild *self = G_CANCELLABLE_CHILD (cancellable); + ConnectionData *connection_data; + + connection_data = g_atomic_pointer_exchange (&self->connection_data, NULL); + + if (connection_data) + g_cancellable_disconnect (self->parent_cancellable, connection_data->id); + + G_CANCELLABLE_CLASS (g_cancellable_child_parent_class)->cancel (cancellable); +} + +static void +g_cancellable_child_dispose (GObject *object) +{ + GCancellableChild *self = G_CANCELLABLE_CHILD (object); + ConnectionData *connection_data; + + connection_data = g_atomic_pointer_exchange (&self->connection_data, NULL); + + if (connection_data && + g_atomic_pointer_compare_and_exchange (&connection_data->self, self, NULL)) + { + gulong connection_id = connection_data->id; + + if (G_UNLIKELY (g_cancellable_is_cancelled (self->parent_cancellable))) + { + /* We do not disconnect from cancellation event if parent has been + * cancelled, as it means we're likely disposing this object during + * the parent cancellation callback. And in such case the parent is + * already cancelled but our callback handler has not been yet called. + * In such case we should still disconnect from the connection not to + * leak, but we're doing it at next idle cycle, while we won't keep + * any further reference on the parent cancellable. + */ + disconnect_from_parent_idle (self->parent_cancellable, connection_id); + } + else + { + g_cancellable_disconnect (self->parent_cancellable, connection_id); + } + } + + g_clear_object (&self->parent_cancellable); + + G_OBJECT_CLASS (g_cancellable_child_parent_class)->dispose (object); +} + +static void +g_cancellable_child_constructed (GObject *object) +{ + GCancellableChild *self = G_CANCELLABLE_CHILD (object); + + if (g_cancellable_is_cancelled (self->parent_cancellable)) + { + g_cancellable_cancel (G_CANCELLABLE (self)); + } + else + { + ConnectionData *connection_data = g_new (ConnectionData, 1); + + /* We need to keep a reference on data to avoid races during callback */ + self->connection_data = connection_data; + self->connection_data->self = self; + self->connection_data->id = + g_cancellable_connect (self->parent_cancellable, + G_CALLBACK (on_parent_cancellable_cancelled), + g_steal_pointer (&connection_data), + (GDestroyNotify) connection_data_free); + } +} + +static void +g_cancellable_child_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GCancellableChild *self = G_CANCELLABLE_CHILD (object); + + switch (prop_id) + { + case PROP_PARENT: + g_value_set_object (value, self->parent_cancellable); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_cancellable_child_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GCancellableChild *self = G_CANCELLABLE_CHILD (object); + + switch (prop_id) + { + case PROP_PARENT: + g_set_object (&self->parent_cancellable, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_cancellable_child_class_init (GCancellableChildClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = g_cancellable_child_get_property; + gobject_class->set_property = g_cancellable_child_set_property; + gobject_class->constructed = g_cancellable_child_constructed; + gobject_class->dispose = g_cancellable_child_dispose; + + G_CANCELLABLE_CLASS (klass)->cancel = g_cancellable_child_cancel; + + /** + * GCancellableChild:parent: + * + * The parent cancellable. + */ + g_object_class_install_property (gobject_class, PROP_PARENT, + g_param_spec_object ("parent", + P_("parent"), + P_("The Parent cancellable"), + G_TYPE_CANCELLABLE, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); +} + +static void +g_cancellable_child_init (GCancellableChild *self) +{ +} diff --git a/gio/gcancellablechild.h b/gio/gcancellablechild.h new file mode 100644 index 000000000..686c6c50f --- /dev/null +++ b/gio/gcancellablechild.h @@ -0,0 +1,57 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2022 Canonical Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: Marco Trevisan + */ + +#ifndef __G_CANCELLABLE_CHILD_H__ +#define __G_CANCELLABLE_CHILD_H__ + +#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define G_TYPE_CANCELLABLE_CHILD (g_cancellable_child_get_type ()) +#define G_CANCELLABLE_CHILD(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_CANCELLABLE_CHILD, GCancellableChild)) +#define G_IS_CANCELLABLE_CHILD(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_CANCELLABLE_CHILD)) + +/** + * GCancellableChild: + * + * Allows actions to be cancelled monitoring a parent cancellable. + */ +typedef struct _GCancellableChild GCancellableChild; + +GIO_AVAILABLE_IN_2_76 +GType g_cancellable_child_get_type (void) G_GNUC_CONST; + +GIO_AVAILABLE_IN_2_76 +GCancellableChild *g_cancellable_child_new (GCancellable *parent); + +GIO_AVAILABLE_IN_2_76 +GCancellable *g_cancellable_child_get_parent (GCancellableChild *cancellable_child); + +G_END_DECLS + +#endif /* __G_CANCELLABLE_CHILD_H__ */ + diff --git a/gio/gio-autocleanups.h b/gio/gio-autocleanups.h index 315288e68..9da123bc0 100644 --- a/gio/gio-autocleanups.h +++ b/gio/gio-autocleanups.h @@ -38,6 +38,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GBufferedInputStream, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GBufferedOutputStream, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GBytesIcon, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GCancellable, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GCancellableChild, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GCharsetConverter, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GConverter, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GConverterInputStream, g_object_unref) diff --git a/gio/gio.h b/gio/gio.h index c17657db0..231861a5d 100644 --- a/gio/gio.h +++ b/gio/gio.h @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include diff --git a/gio/meson.build b/gio/meson.build index 59c2b0fc0..291252a16 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -484,6 +484,7 @@ gio_base_sources = files( 'gbufferedoutputstream.c', 'gbytesicon.c', 'gcancellable.c', + 'gcancellablechild.c', 'gcharsetconverter.c', 'gcontextspecificgroup.c', 'gconverter.c', @@ -642,6 +643,7 @@ gio_headers = files( 'gbufferedoutputstream.h', 'gbytesicon.h', 'gcancellable.h', + 'gcancellablechild.h', 'gcontenttype.h', 'gcharsetconverter.h', 'gconverter.h', diff --git a/gio/tests/autoptr.c b/gio/tests/autoptr.c index d98e98fa6..1ad9f3b44 100644 --- a/gio/tests/autoptr.c +++ b/gio/tests/autoptr.c @@ -6,12 +6,14 @@ test_autoptr (void) g_autoptr(GFile) p = g_file_new_for_path ("/blah"); g_autoptr(GInetAddress) a = g_inet_address_new_from_string ("127.0.0.1"); g_autoptr(GCancellable) cancellable = g_cancellable_new (); + g_autoptr(GCancellableChild) cancellable_child = g_cancellable_child_new (cancellable); g_autofree gchar *path = g_file_get_path (p); g_autofree gchar *istr = g_inet_address_to_string (a); g_assert_cmpstr (path, ==, G_DIR_SEPARATOR_S "blah"); g_assert_cmpstr (istr, ==, "127.0.0.1"); g_assert_false (g_cancellable_is_cancelled (cancellable)); + g_assert_true (g_cancellable_child_get_parent (cancellable_child) == cancellable); } int diff --git a/gio/tests/cancellable.c b/gio/tests/cancellable.c index 6d4d5111c..e467bd653 100644 --- a/gio/tests/cancellable.c +++ b/gio/tests/cancellable.c @@ -1,6 +1,7 @@ /* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2011 Collabora Ltd. + * Copyright (C) 2022 Canonical Ltd. * * SPDX-License-Identifier: LGPL-2.1-or-later * @@ -18,6 +19,7 @@ * Public License along with this library; if not, see . * * Author: Stef Walter + * Marco Trevisan */ #include @@ -31,6 +33,14 @@ static gint num_async_operations = 0; +static gulong +get_first_cancelled_signal_handler_id (GCancellable *cancellable) +{ + guint signal_id = g_signal_lookup ("cancelled", G_TYPE_CANCELLABLE); + + return g_signal_handler_find (cancellable, G_SIGNAL_MATCH_ID, signal_id, 0, NULL, NULL, NULL); +} + typedef struct { guint iterations_requested; /* construct-only */ @@ -885,6 +895,432 @@ test_cancellable_wrong_cancel_override (void) g_object_unref (bad); } +static void +test_cancellable_child_invalid (void) +{ + GCancellableChild *child; + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*G_IS_CANCELLABLE*failed*"); + child = g_cancellable_child_new (NULL); + g_test_assert_expected_messages (); + g_assert_null (child); +} + +static void +test_cancellable_child (void) +{ + GCancellable *parent; + GCancellable *parent_prop; + GCancellableChild *child; + + parent = g_cancellable_new (); + + child = g_cancellable_child_new (parent); + g_assert_true (G_IS_CANCELLABLE (child)); + g_assert_true (g_cancellable_child_get_parent (child) == parent); + g_assert_false (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), !=, 0); + + g_object_get (G_OBJECT (child), "parent", &parent_prop, NULL); + g_assert_true (parent_prop == parent); + + g_object_unref (child); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), ==, 0); + + g_object_unref (parent); + g_object_unref (parent_prop); +} + +static void +test_cancellable_child_chain (void) +{ + GCancellable *root; + GCancellable *parent; + GCancellableChild *child; + GCancellableChild *last_child = NULL; + const guint iterations = 20; + guint i; + + root = g_cancellable_new (); + parent = root; + + for (i = 0; i < iterations; i++) + { + child = g_cancellable_child_new (parent); + + parent = G_CANCELLABLE (child); + g_set_object (&last_child, child); + g_object_unref (child); + } + + child = last_child; + for (i = 0; i < iterations - 1; i++) + { + parent = g_cancellable_child_get_parent (child); + g_assert_true (G_IS_CANCELLABLE_CHILD (parent)); + child = G_CANCELLABLE_CHILD (parent); + } + + parent = g_cancellable_child_get_parent (child); + g_assert_false (G_IS_CANCELLABLE_CHILD (parent)); + g_assert_true (parent == root); + + g_cancellable_cancel (root); + + child = last_child; + for (i = 0; i < iterations - 1; i++) + { + g_assert_true (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + parent = g_cancellable_child_get_parent (child); + child = G_CANCELLABLE_CHILD (parent); + } + + parent = g_cancellable_child_get_parent (child); + g_cancellable_is_cancelled (parent); + g_assert_true (parent == root); + + g_object_unref (root); + g_object_unref (last_child); +} + +static void +test_cancellable_child_already_cancelled (void) +{ + GCancellable *parent; + GCancellableChild *child; + + parent = g_cancellable_new (); + g_cancellable_cancel (parent); + g_assert_true (g_cancellable_is_cancelled (parent)); + + child = g_cancellable_child_new (parent); + g_assert_true (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), ==, 0); + + g_object_unref (child); + g_object_unref (parent); +} + +static void +test_cancellable_child_cancellation (void) +{ + GCancellable *parent; + GCancellableChild *child; + + parent = g_cancellable_new (); + + child = g_cancellable_child_new (parent); + g_assert_false (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), !=, 0); + + g_cancellable_cancel (G_CANCELLABLE (child)); + g_assert_true (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + g_assert_false (g_cancellable_is_cancelled (parent)); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), ==, 0); + + g_object_unref (child); + g_object_unref (parent); +} + +static void +test_cancellable_child_parent_cancellation (void) +{ + GCancellable *parent; + GCancellableChild *child; + + parent = g_cancellable_new (); + + child = g_cancellable_child_new (parent); + g_assert_false (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + + g_cancellable_cancel (parent); + + g_assert_true (g_cancellable_is_cancelled (parent)); + g_assert_true (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), !=, 0); + + g_object_unref (child); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), !=, 0); + + while (get_first_cancelled_signal_handler_id (parent)) + g_main_context_iteration (NULL, TRUE); + + g_object_unref (parent); +} + +static void +test_cancellable_child_parent_cancellation_and_finalization (void) +{ + GCancellable *parent; + GCancellableChild *child; + GWeakRef parent_ref; + + parent = g_cancellable_new (); + + child = g_cancellable_child_new (parent); + g_assert_false (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + + g_cancellable_cancel (parent); + + g_assert_true (g_cancellable_is_cancelled (parent)); + g_assert_true (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), !=, 0); + + g_object_unref (child); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), !=, 0); + + g_weak_ref_init (&parent_ref, parent); + g_object_unref (parent); + g_assert_null (g_weak_ref_get (&parent_ref)); + + /* Ensure we handle the cancellation idle properly */ + g_timeout_add_once (100, (GSourceOnceFunc) g_nullify_pointer, &parent); + + while (parent != NULL) + g_main_context_iteration (NULL, TRUE); +} + +static void +test_cancellable_child_cancel_multiple_concurrent_from_parent (void) +{ + GCancellable *parent; + guint i, iterations; + + parent = g_cancellable_new (); + num_async_operations = 0; + + for (i = 0; i < 45; i++) + { + GCancellableChild *cancellable_child = g_cancellable_child_new (parent); + + iterations = i + 10; + mock_operation_async (iterations, g_random_boolean (), + G_CANCELLABLE (cancellable_child), + on_mock_operation_ready, GUINT_TO_POINTER (iterations)); + num_async_operations++; + + g_object_unref (cancellable_child); + } + + g_assert_cmpint (num_async_operations, ==, 45); + + if (g_test_verbose ()) + g_test_message ("CANCEL: %d operations", num_async_operations); + g_cancellable_cancel (parent); + g_assert_true (g_cancellable_is_cancelled (parent)); + + /* Wait for all operations to be cancelled */ + while (num_async_operations != 0 || + get_first_cancelled_signal_handler_id (parent) != 0) + g_main_context_iteration (NULL, TRUE); + g_assert_cmpint (num_async_operations, ==, 0); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), ==, 0); + + g_object_unref (parent); +} + +static void +test_cancellable_child_cancel_multiple_concurrent_from_root (void) +{ + GCancellable *root; + GCancellable *parent; + guint i, iterations; + + root = g_cancellable_new (); + parent = g_object_ref (root); + num_async_operations = 0; + + for (i = 0; i < 45; i++) + { + GCancellableChild *cancellable_child = g_cancellable_child_new (parent); + + iterations = i + 10; + mock_operation_async (iterations, g_random_boolean (), + G_CANCELLABLE (cancellable_child), + on_mock_operation_ready, GUINT_TO_POINTER (iterations)); + num_async_operations++; + + g_set_object (&parent, G_CANCELLABLE (cancellable_child)); + g_object_unref (cancellable_child); + } + + g_assert_cmpint (num_async_operations, ==, 45); + + if (g_test_verbose ()) + g_test_message ("CANCEL: %d operations", num_async_operations); + g_cancellable_cancel (root); + g_assert_true (g_cancellable_is_cancelled (root)); + + /* Wait for all operations to be cancelled */ + while (num_async_operations != 0 || + get_first_cancelled_signal_handler_id (parent) != 0) + g_main_context_iteration (NULL, TRUE); + g_assert_cmpint (num_async_operations, ==, 0); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), ==, 0); + + g_object_unref (root); + g_object_unref (parent); +} + +static void +on_parent_cancelled_before_dispose (GCancellable *parent, + GCancellableChild **child_ptr) +{ + GCancellableChild *child = *child_ptr; + GWeakRef child_ref; + + g_weak_ref_init (&child_ref, child); + g_assert_false (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + + g_object_unref (child); + g_assert_null (g_weak_ref_get (&child_ref)); + + *child_ptr = NULL; +} + +static void +test_cancellable_child_dispose_before_parent_cancellation (void) +{ + GCancellable *parent; + GCancellableChild *child = NULL; + gulong id; + + parent = g_cancellable_new (); + id = g_cancellable_connect (parent, + G_CALLBACK (on_parent_cancelled_before_dispose), + &child, NULL); + + child = g_cancellable_child_new (parent); + g_assert_false (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + + g_cancellable_cancel (parent); + g_assert_null (child); + + g_cancellable_disconnect (parent, id); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), !=, 0); + + while (get_first_cancelled_signal_handler_id (parent)) + g_main_context_iteration (NULL, TRUE); + + g_object_unref (parent); +} + +static void +on_parent_cancelled_after_dispose (GCancellable *parent, + GCancellableChild **child_ptr) +{ + GCancellableChild *child = *child_ptr; + GWeakRef child_ref; + + g_weak_ref_init (&child_ref, child); + g_assert_true (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + + g_object_unref (child); + g_assert_null (g_weak_ref_get (&child_ref)); + + *child_ptr = NULL; +} + +static void +test_cancellable_child_dispose_after_parent_cancellation (void) +{ + GCancellable *parent; + GCancellableChild *child = NULL; + gulong id; + + parent = g_cancellable_new (); + child = g_cancellable_child_new (parent); + g_assert_false (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + + id = g_cancellable_connect (parent, + G_CALLBACK (on_parent_cancelled_after_dispose), + &child, NULL); + + g_cancellable_cancel (parent); + g_assert_null (child); + + g_cancellable_disconnect (parent, id); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), !=, 0); + + while (get_first_cancelled_signal_handler_id (parent)) + g_main_context_iteration (NULL, TRUE); + + g_object_unref (parent); +} + +static void +on_parent_cancelled_before_cancel (GCancellable *parent, + GCancellableChild *child) +{ + g_assert_false (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + g_cancellable_cancel (G_CANCELLABLE (child)); +} + +static void +test_cancellable_child_cancel_before_parent_cancellation (void) +{ + GCancellable *parent; + GCancellableChild *child = NULL; + gulong id; + + parent = g_cancellable_new (); + id = g_cancellable_connect (parent, + G_CALLBACK (on_parent_cancelled_before_cancel), + child, NULL); + + child = g_cancellable_child_new (parent); + g_assert_false (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + + g_cancellable_cancel (parent); + g_assert_true (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + + g_cancellable_disconnect (parent, id); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), !=, 0); + + while (get_first_cancelled_signal_handler_id (parent)) + g_main_context_iteration (NULL, TRUE); + + g_object_unref (child); + g_object_unref (parent); +} + +static void +on_parent_cancelled_after_cancel (GCancellable *parent, + GCancellableChild *child) +{ + g_assert_true (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + g_cancellable_cancel (G_CANCELLABLE (child)); +} + +static void +test_cancellable_child_cancel_after_parent_cancellation (void) +{ + GCancellable *parent; + GCancellableChild *child = NULL; + gulong id; + + parent = g_cancellable_new (); + child = g_cancellable_child_new (parent); + g_assert_false (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + + id = g_cancellable_connect (parent, + G_CALLBACK (on_parent_cancelled_after_cancel), + child, NULL); + + g_cancellable_cancel (parent); + g_assert_true (g_cancellable_is_cancelled (G_CANCELLABLE (child))); + + g_cancellable_disconnect (parent, id); + g_assert_cmpuint (get_first_cancelled_signal_handler_id (parent), !=, 0); + + while (get_first_cancelled_signal_handler_id (parent)) + g_main_context_iteration (NULL, TRUE); + + g_object_unref (child); + g_object_unref (parent); +} + int main (int argc, char *argv[]) { @@ -902,6 +1338,19 @@ main (int argc, char *argv[]) g_test_add_func ("/cancellable/invalid-cancel-override", test_cancellable_wrong_cancel_override); g_test_add_func ("/cancellable-source/threaded-dispose", test_cancellable_source_threaded_dispose); g_test_add_func ("/cancellable-source/can-be-fired-multiple-times", test_cancellable_source_can_be_fired_multiple_times); + g_test_add_func ("/cancellable-child/invalid", test_cancellable_child_invalid); + g_test_add_func ("/cancellable-child/basic", test_cancellable_child); + g_test_add_func ("/cancellable-child/chain", test_cancellable_child_chain); + g_test_add_func ("/cancellable-child/already-cancelled", test_cancellable_child_already_cancelled); + g_test_add_func ("/cancellable-child/child-cancellation", test_cancellable_child_cancellation); + g_test_add_func ("/cancellable-child/parent-cancellation", test_cancellable_child_parent_cancellation); + g_test_add_func ("/cancellable-child/parent-cancellation-and-finalization", test_cancellable_child_parent_cancellation_and_finalization); + g_test_add_func ("/cancellable-child/threaded-cancellation", test_cancellable_child_cancel_multiple_concurrent_from_parent); + g_test_add_func ("/cancellable-child/threaded-cancellation-chain", test_cancellable_child_cancel_multiple_concurrent_from_root); + g_test_add_func ("/cancellable-child/disposition-before-parent-cancellation", test_cancellable_child_dispose_before_parent_cancellation); + g_test_add_func ("/cancellable-child/disposition-after-parent-cancellation", test_cancellable_child_dispose_after_parent_cancellation); + g_test_add_func ("/cancellable-child/cancellation-before-parent-cancellation", test_cancellable_child_cancel_before_parent_cancellation); + g_test_add_func ("/cancellable-child/cancellation-after-parent-cancellation", test_cancellable_child_cancel_after_parent_cancellation); return g_test_run (); } diff --git a/gio/tests/defaultvalue.c b/gio/tests/defaultvalue.c index bbc8b3ee2..520afe023 100644 --- a/gio/tests/defaultvalue.c +++ b/gio/tests/defaultvalue.c @@ -75,6 +75,7 @@ test_type (gconstpointer data) if (g_type_is_a (type, G_TYPE_BINDING) || g_type_is_a (type, G_TYPE_BUFFERED_INPUT_STREAM) || g_type_is_a (type, G_TYPE_BUFFERED_OUTPUT_STREAM) || + g_type_is_a (type, G_TYPE_CANCELLABLE_CHILD) || g_type_is_a (type, G_TYPE_CHARSET_CONVERTER) || g_type_is_a (type, G_TYPE_DBUS_ACTION_GROUP) || g_type_is_a (type, G_TYPE_DBUS_CONNECTION) ||