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.
This commit is contained in:
Marco Trevisan (Treviño) 2022-06-13 18:48:02 +02:00
parent ab55a59005
commit c62001ec82
8 changed files with 851 additions and 0 deletions

338
gio/gcancellablechild.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Author: Marco Trevisan <marco.trevisan@canonical.com>
*/
#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)
{
}

57
gio/gcancellablechild.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Author: Marco Trevisan <marco.trevisan@canonical.com>
*/
#ifndef __G_CANCELLABLE_CHILD_H__
#define __G_CANCELLABLE_CHILD_H__
#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
#error "Only <gio/gio.h> can be included directly."
#endif
#include <gio/gcancellable.h>
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__ */

View File

@ -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)

View File

@ -40,6 +40,7 @@
#include <gio/gbufferedoutputstream.h>
#include <gio/gbytesicon.h>
#include <gio/gcancellable.h>
#include <gio/gcancellablechild.h>
#include <gio/gcharsetconverter.h>
#include <gio/gcontenttype.h>
#include <gio/gconverter.h>

View File

@ -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',

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Author: Stef Walter <stefw@collabora.co.uk>
* Marco Trevisan <marco.trevisan@canonical.com>
*/
#include <locale.h>
@ -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 ();
}

View File

@ -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) ||