glib/gio/gcancellablechild.c
Marco Trevisan (Treviño) c62001ec82 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.
2024-05-05 17:12:21 +02:00

339 lines
10 KiB
C

/* 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)
{
}