mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-08-23 09:46:13 +02:00
Merge branch 'cancellable-child' into 'main'
gio: Add GCancellableChild, a GCancellable with a GCancellable parent See merge request GNOME/glib!2746
This commit is contained in:
commit
82b0b2c933
@ -1,6 +1,7 @@
|
||||
/* GIO - GLib Input, Output and Streaming Library
|
||||
*
|
||||
* Copyright (C) 2006-2007 Red Hat, Inc.
|
||||
* Copyright (C) 2022-2024 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: Alexander Larsson <alexl@redhat.com>
|
||||
* Author: Marco Trevisan <marco.trevisan@canonical.com>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
@ -27,6 +29,7 @@
|
||||
#include "gcancellable.h"
|
||||
#include "glibintl.h"
|
||||
|
||||
static void g_cancellable_real_cancel (GCancellable *);
|
||||
|
||||
/**
|
||||
* GCancellable:
|
||||
@ -45,14 +48,12 @@ enum {
|
||||
|
||||
struct _GCancellablePrivate
|
||||
{
|
||||
/* Atomic so that g_cancellable_is_cancelled does not require holding the mutex. */
|
||||
/* Atomic so that we don't require holding global mutexes for independent ops. */
|
||||
gboolean cancelled;
|
||||
/* Access to fields below is protected by cancellable_mutex. */
|
||||
guint cancelled_running : 1;
|
||||
guint cancelled_running_waiting : 1;
|
||||
unsigned cancelled_emissions;
|
||||
unsigned cancelled_emissions_waiting : 1;
|
||||
int cancelled_running;
|
||||
|
||||
/* Access to fields below is protected by cancellable's mutex. */
|
||||
GMutex mutex;
|
||||
guint fd_refcount;
|
||||
GWakeup *wakeup;
|
||||
};
|
||||
@ -62,7 +63,6 @@ static guint signals[LAST_SIGNAL] = { 0 };
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (GCancellable, g_cancellable, G_TYPE_OBJECT)
|
||||
|
||||
static GPrivate current_cancellable;
|
||||
static GMutex cancellable_mutex;
|
||||
static GCond cancellable_cond;
|
||||
|
||||
static void
|
||||
@ -70,9 +70,15 @@ g_cancellable_finalize (GObject *object)
|
||||
{
|
||||
GCancellable *cancellable = G_CANCELLABLE (object);
|
||||
|
||||
/* We're at finalization phase, so only one thread can be here.
|
||||
* Thus there's no need to lock. In case something is locking us, then we've
|
||||
* a bug, and g_mutex_clear() will make this clear aborting.
|
||||
*/
|
||||
if (cancellable->priv->wakeup)
|
||||
GLIB_PRIVATE_CALL (g_wakeup_free) (cancellable->priv->wakeup);
|
||||
|
||||
g_mutex_clear (&cancellable->priv->mutex);
|
||||
|
||||
G_OBJECT_CLASS (g_cancellable_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
@ -81,6 +87,8 @@ g_cancellable_class_init (GCancellableClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
klass->cancel = g_cancellable_real_cancel;
|
||||
|
||||
gobject_class->finalize = g_cancellable_finalize;
|
||||
|
||||
/**
|
||||
@ -154,6 +162,8 @@ static void
|
||||
g_cancellable_init (GCancellable *cancellable)
|
||||
{
|
||||
cancellable->priv = g_cancellable_get_instance_private (cancellable);
|
||||
|
||||
g_mutex_init (&cancellable->priv->mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -265,28 +275,17 @@ g_cancellable_reset (GCancellable *cancellable)
|
||||
|
||||
g_return_if_fail (G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
|
||||
priv = cancellable->priv;
|
||||
|
||||
while (priv->cancelled_running || priv->cancelled_emissions > 0)
|
||||
{
|
||||
if (priv->cancelled_running)
|
||||
priv->cancelled_running_waiting = TRUE;
|
||||
g_mutex_lock (&priv->mutex);
|
||||
|
||||
if (priv->cancelled_emissions > 0)
|
||||
priv->cancelled_emissions_waiting = TRUE;
|
||||
|
||||
g_cond_wait (&cancellable_cond, &cancellable_mutex);
|
||||
}
|
||||
|
||||
if (g_atomic_int_exchange (&priv->cancelled, FALSE))
|
||||
if (g_atomic_int_compare_and_exchange (&priv->cancelled, TRUE, FALSE))
|
||||
{
|
||||
if (priv->wakeup)
|
||||
GLIB_PRIVATE_CALL (g_wakeup_acknowledge) (priv->wakeup);
|
||||
}
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
g_mutex_unlock (&priv->mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -404,26 +403,29 @@ g_cancellable_get_fd (GCancellable *cancellable)
|
||||
gboolean
|
||||
g_cancellable_make_pollfd (GCancellable *cancellable, GPollFD *pollfd)
|
||||
{
|
||||
GCancellablePrivate *priv;
|
||||
|
||||
g_return_val_if_fail (pollfd != NULL, FALSE);
|
||||
if (cancellable == NULL)
|
||||
return FALSE;
|
||||
g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), FALSE);
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
priv = cancellable->priv;
|
||||
|
||||
cancellable->priv->fd_refcount++;
|
||||
g_mutex_lock (&priv->mutex);
|
||||
|
||||
if (cancellable->priv->wakeup == NULL)
|
||||
if ((priv->fd_refcount++) == 0)
|
||||
{
|
||||
cancellable->priv->wakeup = GLIB_PRIVATE_CALL (g_wakeup_new) ();
|
||||
priv->wakeup = GLIB_PRIVATE_CALL (g_wakeup_new) ();
|
||||
|
||||
if (g_atomic_int_get (&cancellable->priv->cancelled))
|
||||
GLIB_PRIVATE_CALL (g_wakeup_signal) (cancellable->priv->wakeup);
|
||||
if (g_atomic_int_get (&priv->cancelled))
|
||||
GLIB_PRIVATE_CALL (g_wakeup_signal) (priv->wakeup);
|
||||
}
|
||||
|
||||
GLIB_PRIVATE_CALL (g_wakeup_get_pollfd) (cancellable->priv->wakeup, pollfd);
|
||||
g_assert (priv->wakeup);
|
||||
GLIB_PRIVATE_CALL (g_wakeup_get_pollfd) (priv->wakeup, pollfd);
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
g_mutex_unlock (&priv->mutex);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@ -447,26 +449,22 @@ g_cancellable_make_pollfd (GCancellable *cancellable, GPollFD *pollfd)
|
||||
void
|
||||
g_cancellable_release_fd (GCancellable *cancellable)
|
||||
{
|
||||
GCancellablePrivate *priv;
|
||||
|
||||
if (cancellable == NULL)
|
||||
return;
|
||||
|
||||
g_return_if_fail (G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
priv = cancellable->priv;
|
||||
g_mutex_lock (&cancellable->priv->mutex);
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
g_assert (priv->fd_refcount > 0);
|
||||
g_assert (cancellable->priv->fd_refcount > 0);
|
||||
|
||||
priv->fd_refcount--;
|
||||
if (priv->fd_refcount == 0)
|
||||
if ((cancellable->priv->fd_refcount--) == 1)
|
||||
{
|
||||
GLIB_PRIVATE_CALL (g_wakeup_free) (priv->wakeup);
|
||||
priv->wakeup = NULL;
|
||||
GLIB_PRIVATE_CALL (g_wakeup_free) (cancellable->priv->wakeup);
|
||||
cancellable->priv->wakeup = NULL;
|
||||
}
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
g_mutex_unlock (&cancellable->priv->mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -493,39 +491,52 @@ g_cancellable_release_fd (GCancellable *cancellable)
|
||||
void
|
||||
g_cancellable_cancel (GCancellable *cancellable)
|
||||
{
|
||||
GCancellablePrivate *priv;
|
||||
|
||||
if (cancellable == NULL || g_cancellable_is_cancelled (cancellable))
|
||||
return;
|
||||
|
||||
G_CANCELLABLE_GET_CLASS (cancellable)->cancel (cancellable);
|
||||
|
||||
if (G_CANCELLABLE_GET_CLASS (cancellable)->cancel != g_cancellable_real_cancel)
|
||||
{
|
||||
if (!g_atomic_int_get (&cancellable->priv->cancelled))
|
||||
{
|
||||
g_critical ("Overridden cancel function did not call parent hook");
|
||||
g_cancellable_real_cancel (cancellable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
g_cancellable_real_cancel (GCancellable *cancellable)
|
||||
{
|
||||
GCancellablePrivate *priv;
|
||||
|
||||
priv = cancellable->priv;
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
/* We add a reference before locking, to avoid that potential toggle
|
||||
* notifications on the object might happen while we're locked.
|
||||
*/
|
||||
g_object_ref (cancellable);
|
||||
g_mutex_lock (&priv->mutex);
|
||||
|
||||
if (g_atomic_int_exchange (&priv->cancelled, TRUE))
|
||||
if (!g_atomic_int_compare_and_exchange (&priv->cancelled, FALSE, TRUE))
|
||||
{
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
g_mutex_unlock (&priv->mutex);
|
||||
g_object_unref (cancellable);
|
||||
return;
|
||||
}
|
||||
|
||||
priv->cancelled_running = TRUE;
|
||||
g_atomic_int_inc (&priv->cancelled_running);
|
||||
|
||||
if (priv->wakeup)
|
||||
GLIB_PRIVATE_CALL (g_wakeup_signal) (priv->wakeup);
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
|
||||
g_object_ref (cancellable);
|
||||
g_signal_emit (cancellable, signals[CANCELLED], 0);
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
|
||||
priv->cancelled_running = FALSE;
|
||||
if (priv->cancelled_running_waiting)
|
||||
if (g_atomic_int_dec_and_test (&priv->cancelled_running))
|
||||
g_cond_broadcast (&cancellable_cond);
|
||||
priv->cancelled_running_waiting = FALSE;
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
g_mutex_unlock (&priv->mutex);
|
||||
|
||||
g_object_unref (cancellable);
|
||||
}
|
||||
@ -571,7 +582,7 @@ g_cancellable_connect (GCancellable *cancellable,
|
||||
|
||||
g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), 0);
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
g_mutex_lock (&cancellable->priv->mutex);
|
||||
|
||||
if (g_atomic_int_get (&cancellable->priv->cancelled))
|
||||
{
|
||||
@ -581,23 +592,10 @@ g_cancellable_connect (GCancellable *cancellable,
|
||||
_callback = (void *)callback;
|
||||
id = 0;
|
||||
|
||||
cancellable->priv->cancelled_emissions++;
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
|
||||
_callback (cancellable, data);
|
||||
|
||||
if (data_destroy_func)
|
||||
data_destroy_func (data);
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
|
||||
if (cancellable->priv->cancelled_emissions_waiting)
|
||||
g_cond_broadcast (&cancellable_cond);
|
||||
|
||||
cancellable->priv->cancelled_emissions--;
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -605,10 +603,9 @@ g_cancellable_connect (GCancellable *cancellable,
|
||||
callback, data,
|
||||
(GClosureNotify) data_destroy_func,
|
||||
G_CONNECT_DEFAULT);
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
}
|
||||
|
||||
g_mutex_unlock (&cancellable->priv->mutex);
|
||||
|
||||
return id;
|
||||
}
|
||||
@ -644,33 +641,26 @@ g_cancellable_disconnect (GCancellable *cancellable,
|
||||
if (handler_id == 0 || cancellable == NULL)
|
||||
return;
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
|
||||
priv = cancellable->priv;
|
||||
|
||||
while (priv->cancelled_running || priv->cancelled_emissions)
|
||||
{
|
||||
if (priv->cancelled_running)
|
||||
priv->cancelled_running_waiting = TRUE;
|
||||
g_mutex_lock (&priv->mutex);
|
||||
|
||||
if (priv->cancelled_emissions)
|
||||
priv->cancelled_emissions_waiting = TRUE;
|
||||
while (g_atomic_int_get (&priv->cancelled_running) != 0)
|
||||
g_cond_wait (&cancellable_cond, &priv->mutex);
|
||||
|
||||
g_cond_wait (&cancellable_cond, &cancellable_mutex);
|
||||
}
|
||||
g_mutex_unlock (&priv->mutex);
|
||||
|
||||
g_signal_handler_disconnect (cancellable, handler_id);
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GSource source;
|
||||
|
||||
/* Atomic: */
|
||||
GCancellable *cancellable;
|
||||
gulong cancelled_handler;
|
||||
/* Protected by cancellable_mutex: */
|
||||
gboolean resurrected_during_cancellation;
|
||||
/* Atomic: */
|
||||
gboolean cancelled_callback_called;
|
||||
} GCancellableSource;
|
||||
|
||||
/*
|
||||
@ -691,26 +681,32 @@ cancellable_source_cancelled (GCancellable *cancellable,
|
||||
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_assert (g_atomic_int_compare_and_exchange (
|
||||
&cancellable_source->cancelled_callback_called, FALSE, TRUE));
|
||||
|
||||
g_source_unref (source);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
cancellable_source_prepare (GSource *source,
|
||||
gint *timeout)
|
||||
{
|
||||
GCancellableSource *cancellable_source = (GCancellableSource *) source;
|
||||
GCancellable *cancellable;
|
||||
|
||||
if (timeout)
|
||||
*timeout = -1;
|
||||
|
||||
cancellable = g_atomic_pointer_get (&cancellable_source->cancellable);
|
||||
if (cancellable && !g_atomic_int_get (&cancellable->priv->cancelled_running))
|
||||
g_atomic_int_set (&cancellable_source->cancelled_callback_called, FALSE);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
cancellable_source_dispatch (GSource *source,
|
||||
GSourceFunc callback,
|
||||
@ -727,12 +723,13 @@ static void
|
||||
cancellable_source_dispose (GSource *source)
|
||||
{
|
||||
GCancellableSource *cancellable_source = (GCancellableSource *)source;
|
||||
GCancellable *cancellable;
|
||||
|
||||
g_mutex_lock (&cancellable_mutex);
|
||||
cancellable = g_atomic_pointer_exchange (&cancellable_source->cancellable, NULL);
|
||||
|
||||
if (cancellable_source->cancellable)
|
||||
if (cancellable)
|
||||
{
|
||||
if (cancellable_source->cancellable->priv->cancelled_running)
|
||||
if (g_atomic_int_get (&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
|
||||
@ -744,21 +741,21 @@ cancellable_source_dispose (GSource *source)
|
||||
* 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.
|
||||
* Eliminate that race by waiting to ensure that our cancelled
|
||||
* callback has been called, keeping a temporary ref, so that
|
||||
* there's no risk that we're unreffing something that is still
|
||||
* going to be used.
|
||||
*/
|
||||
|
||||
g_source_ref (source);
|
||||
cancellable_source->resurrected_during_cancellation = TRUE;
|
||||
while (!g_atomic_int_get (&cancellable_source->cancelled_callback_called))
|
||||
;
|
||||
g_source_unref (source);
|
||||
}
|
||||
|
||||
g_clear_signal_handler (&cancellable_source->cancelled_handler,
|
||||
cancellable_source->cancellable);
|
||||
g_clear_object (&cancellable_source->cancellable);
|
||||
g_clear_signal_handler (&cancellable_source->cancelled_handler, cancellable);
|
||||
g_object_unref (cancellable);
|
||||
}
|
||||
|
||||
g_mutex_unlock (&cancellable_mutex);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -787,7 +784,7 @@ cancellable_source_closure_callback (GCancellable *cancellable,
|
||||
|
||||
static GSourceFuncs cancellable_source_funcs =
|
||||
{
|
||||
NULL,
|
||||
cancellable_source_prepare,
|
||||
NULL,
|
||||
cancellable_source_dispatch,
|
||||
NULL,
|
||||
|
@ -54,6 +54,7 @@ struct _GCancellableClass
|
||||
GObjectClass parent_class;
|
||||
|
||||
void (* cancelled) (GCancellable *cancellable);
|
||||
void (* cancel) (GCancellable *cancellable);
|
||||
|
||||
/*< private >*/
|
||||
/* Padding for future expansion */
|
||||
@ -61,7 +62,6 @@ struct _GCancellableClass
|
||||
void (*_g_reserved2) (void);
|
||||
void (*_g_reserved3) (void);
|
||||
void (*_g_reserved4) (void);
|
||||
void (*_g_reserved5) (void);
|
||||
};
|
||||
|
||||
GIO_AVAILABLE_IN_ALL
|
||||
|
338
gio/gcancellablechild.c
Normal file
338
gio/gcancellablechild.c
Normal 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
57
gio/gcancellablechild.h
Normal 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__ */
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include <glib.h>
|
||||
#include <gio.h>
|
||||
#include "glibintl.h"
|
||||
|
||||
#include "giostream.h"
|
||||
@ -692,8 +693,6 @@ typedef struct
|
||||
GIOStream *stream2;
|
||||
GIOStreamSpliceFlags flags;
|
||||
gint io_priority;
|
||||
GCancellable *cancellable;
|
||||
gulong cancelled_id;
|
||||
GCancellable *op1_cancellable;
|
||||
GCancellable *op2_cancellable;
|
||||
guint completed;
|
||||
@ -705,10 +704,8 @@ splice_context_free (SpliceContext *ctx)
|
||||
{
|
||||
g_object_unref (ctx->stream1);
|
||||
g_object_unref (ctx->stream2);
|
||||
if (ctx->cancellable != NULL)
|
||||
g_object_unref (ctx->cancellable);
|
||||
g_object_unref (ctx->op1_cancellable);
|
||||
g_object_unref (ctx->op2_cancellable);
|
||||
g_clear_object (&ctx->op1_cancellable);
|
||||
g_clear_object (&ctx->op2_cancellable);
|
||||
g_clear_error (&ctx->error);
|
||||
g_slice_free (SpliceContext, ctx);
|
||||
}
|
||||
@ -717,10 +714,6 @@ static void
|
||||
splice_complete (GTask *task,
|
||||
SpliceContext *ctx)
|
||||
{
|
||||
if (ctx->cancelled_id != 0)
|
||||
g_cancellable_disconnect (ctx->cancellable, ctx->cancelled_id);
|
||||
ctx->cancelled_id = 0;
|
||||
|
||||
if (ctx->error != NULL)
|
||||
{
|
||||
g_task_return_error (task, ctx->error);
|
||||
@ -762,6 +755,7 @@ splice_cb (GObject *ostream,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask *task = user_data;
|
||||
GCancellable *task_cancellable = g_task_get_cancellable (task);
|
||||
SpliceContext *ctx = g_task_get_task_data (task);
|
||||
GError *error = NULL;
|
||||
|
||||
@ -772,8 +766,8 @@ splice_cb (GObject *ostream,
|
||||
/* ignore cancellation error if it was not requested by the user */
|
||||
if (error != NULL &&
|
||||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
|
||||
(ctx->cancellable == NULL ||
|
||||
!g_cancellable_is_cancelled (ctx->cancellable)))
|
||||
(task_cancellable == NULL ||
|
||||
!g_cancellable_is_cancelled (task_cancellable)))
|
||||
g_clear_error (&error);
|
||||
|
||||
/* Keep the first error that occurred */
|
||||
@ -791,12 +785,11 @@ splice_cb (GObject *ostream,
|
||||
}
|
||||
else if (ctx->completed == 2)
|
||||
{
|
||||
if (ctx->cancellable == NULL ||
|
||||
!g_cancellable_is_cancelled (ctx->cancellable))
|
||||
{
|
||||
g_cancellable_reset (ctx->op1_cancellable);
|
||||
g_cancellable_reset (ctx->op2_cancellable);
|
||||
}
|
||||
if (ctx->op1_cancellable && !g_cancellable_is_cancelled (ctx->op1_cancellable))
|
||||
g_cancellable_reset (ctx->op1_cancellable);
|
||||
|
||||
if (ctx->op2_cancellable && !g_cancellable_is_cancelled (ctx->op2_cancellable))
|
||||
g_cancellable_reset (ctx->op2_cancellable);
|
||||
|
||||
/* Close the IO streams if needed */
|
||||
if ((ctx->flags & G_IO_STREAM_SPLICE_CLOSE_STREAM1) != 0)
|
||||
@ -827,17 +820,6 @@ splice_cb (GObject *ostream,
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
static void
|
||||
splice_cancelled_cb (GCancellable *cancellable,
|
||||
GTask *task)
|
||||
{
|
||||
SpliceContext *ctx;
|
||||
|
||||
ctx = g_task_get_task_data (task);
|
||||
g_cancellable_cancel (ctx->op1_cancellable);
|
||||
g_cancellable_cancel (ctx->op2_cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_io_stream_splice_async:
|
||||
* @stream1: a #GIOStream.
|
||||
@ -886,8 +868,6 @@ g_io_stream_splice_async (GIOStream *stream1,
|
||||
ctx->stream1 = g_object_ref (stream1);
|
||||
ctx->stream2 = g_object_ref (stream2);
|
||||
ctx->flags = flags;
|
||||
ctx->op1_cancellable = g_cancellable_new ();
|
||||
ctx->op2_cancellable = g_cancellable_new ();
|
||||
ctx->completed = 0;
|
||||
|
||||
task = g_task_new (NULL, cancellable, callback, user_data);
|
||||
@ -896,10 +876,8 @@ g_io_stream_splice_async (GIOStream *stream1,
|
||||
|
||||
if (cancellable != NULL)
|
||||
{
|
||||
ctx->cancellable = g_object_ref (cancellable);
|
||||
ctx->cancelled_id = g_cancellable_connect (cancellable,
|
||||
G_CALLBACK (splice_cancelled_cb), g_object_ref (task),
|
||||
g_object_unref);
|
||||
ctx->op1_cancellable = G_CANCELLABLE (g_cancellable_child_new (cancellable));
|
||||
ctx->op2_cancellable = G_CANCELLABLE (g_cancellable_child_new (cancellable));
|
||||
}
|
||||
|
||||
istream = g_io_stream_get_input_stream (stream1);
|
||||
|
@ -41,7 +41,7 @@
|
||||
#include <gio/gproxyaddressenumerator.h>
|
||||
#include <gio/gproxyaddress.h>
|
||||
#include <gio/gtask.h>
|
||||
#include <gio/gcancellable.h>
|
||||
#include <gio/gcancellablechild.h>
|
||||
#include <gio/gioerror.h>
|
||||
#include <gio/gsocket.h>
|
||||
#include <gio/gnetworkaddress.h>
|
||||
@ -1496,8 +1496,6 @@ typedef struct
|
||||
GSocketConnectable *connectable;
|
||||
GSocketAddressEnumerator *enumerator;
|
||||
GCancellable *enumeration_cancellable;
|
||||
GCancellable *enumeration_parent_cancellable; /* (nullable) (owned) */
|
||||
gulong enumeration_cancelled_id;
|
||||
|
||||
GSList *connection_attempts; /* (element-type ConnectionAttempt) (owned) */
|
||||
GSList *successful_connections;
|
||||
@ -1521,9 +1519,6 @@ g_socket_client_async_connect_data_free (GSocketClientAsyncConnectData *data)
|
||||
g_clear_object (&data->connectable);
|
||||
g_clear_object (&data->enumerator);
|
||||
|
||||
g_cancellable_disconnect (data->enumeration_parent_cancellable, data->enumeration_cancelled_id);
|
||||
g_clear_object (&data->enumeration_parent_cancellable);
|
||||
data->enumeration_cancelled_id = 0;
|
||||
g_clear_object (&data->enumeration_cancellable);
|
||||
|
||||
g_slist_free_full (data->connection_attempts, connection_attempt_unref);
|
||||
@ -1544,8 +1539,6 @@ typedef struct
|
||||
GSource *delay_timeout_source; /* (owned) */
|
||||
gboolean delay_reached;
|
||||
GCancellable *cancellable;
|
||||
GCancellable *task_cancellable; /* (owned); this is equal to g_task_get_cancellable (ConnectionAttempt.data->task), but with a longer lifetime */
|
||||
gulong cancelled_id;
|
||||
grefcount ref;
|
||||
} ConnectionAttempt;
|
||||
|
||||
@ -1573,9 +1566,6 @@ connection_attempt_unref (gpointer pointer)
|
||||
g_clear_object (&attempt->address);
|
||||
g_clear_object (&attempt->socket);
|
||||
g_clear_object (&attempt->connection);
|
||||
g_cancellable_disconnect (attempt->task_cancellable, attempt->cancelled_id);
|
||||
g_clear_object (&attempt->task_cancellable);
|
||||
attempt->cancelled_id = 0;
|
||||
g_clear_object (&attempt->cancellable);
|
||||
g_clear_object (&attempt->proxy_addr);
|
||||
if (attempt->delay_timeout_source)
|
||||
@ -2001,15 +1991,6 @@ on_connection_attempt_delay_reached (gpointer data)
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_connection_cancelled (GCancellable *cancellable,
|
||||
gpointer data)
|
||||
{
|
||||
GCancellable *linked_cancellable = G_CANCELLABLE (data);
|
||||
|
||||
g_cancellable_cancel (linked_cancellable);
|
||||
}
|
||||
|
||||
static void
|
||||
g_socket_client_enumerator_callback (GObject *object,
|
||||
GAsyncResult *result,
|
||||
@ -2079,7 +2060,6 @@ g_socket_client_enumerator_callback (GObject *object,
|
||||
attempt->data = data;
|
||||
attempt->socket = socket;
|
||||
attempt->address = address;
|
||||
attempt->cancellable = g_cancellable_new ();
|
||||
attempt->connection = (GIOStream *)g_socket_connection_factory_create_connection (socket);
|
||||
attempt->delay_timeout_source = g_timeout_source_new (HAPPY_EYEBALLS_CONNECTION_ATTEMPT_DELAY_MS);
|
||||
|
||||
@ -2095,10 +2075,8 @@ g_socket_client_enumerator_callback (GObject *object,
|
||||
|
||||
if (g_task_get_cancellable (data->task))
|
||||
{
|
||||
attempt->task_cancellable = g_object_ref (g_task_get_cancellable (data->task));
|
||||
attempt->cancelled_id =
|
||||
g_cancellable_connect (attempt->task_cancellable, G_CALLBACK (on_connection_cancelled),
|
||||
g_object_ref (attempt->cancellable), g_object_unref);
|
||||
attempt->cancellable =
|
||||
G_CANCELLABLE (g_cancellable_child_new (g_task_get_cancellable (data->task)));
|
||||
}
|
||||
|
||||
g_socket_connection_set_cached_remote_address ((GSocketConnection *)attempt->connection, address);
|
||||
@ -2206,14 +2184,8 @@ g_socket_client_connect_async (GSocketClient *client,
|
||||
g_task_set_source_tag (data->task, g_socket_client_connect_async);
|
||||
g_task_set_task_data (data->task, data, (GDestroyNotify)g_socket_client_async_connect_data_free);
|
||||
|
||||
data->enumeration_cancellable = g_cancellable_new ();
|
||||
if (cancellable)
|
||||
{
|
||||
data->enumeration_parent_cancellable = g_object_ref (cancellable);
|
||||
data->enumeration_cancelled_id =
|
||||
g_cancellable_connect (cancellable, G_CALLBACK (on_connection_cancelled),
|
||||
g_object_ref (data->enumeration_cancellable), g_object_unref);
|
||||
}
|
||||
data->enumeration_cancellable = G_CANCELLABLE (g_cancellable_child_new (cancellable));
|
||||
|
||||
g_debug ("%s: starting new g_socket_client_connect_async() with GTask %p "
|
||||
"and GSocketClientAsyncConnectData %p",
|
||||
|
@ -488,6 +488,7 @@ gio_base_sources = files(
|
||||
'gbufferedoutputstream.c',
|
||||
'gbytesicon.c',
|
||||
'gcancellable.c',
|
||||
'gcancellablechild.c',
|
||||
'gcharsetconverter.c',
|
||||
'gcontextspecificgroup.c',
|
||||
'gconverter.c',
|
||||
@ -646,6 +647,7 @@ gio_headers = files(
|
||||
'gbufferedoutputstream.h',
|
||||
'gbytesicon.h',
|
||||
'gcancellable.h',
|
||||
'gcancellablechild.h',
|
||||
'gcontenttype.h',
|
||||
'gcharsetconverter.h',
|
||||
'gconverter.h',
|
||||
|
@ -5,11 +5,15 @@ 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
|
||||
|
@ -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>
|
||||
@ -32,6 +34,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 */
|
||||
@ -190,6 +200,7 @@ test_cancel_multiple_concurrent (void)
|
||||
}
|
||||
|
||||
cancellable = g_cancellable_new ();
|
||||
num_async_operations = 0;
|
||||
|
||||
for (i = 0; i < 45; i++)
|
||||
{
|
||||
@ -262,11 +273,6 @@ threaded_dispose_thread_cb (gpointer user_data)
|
||||
static void
|
||||
test_cancellable_source_threaded_dispose (void)
|
||||
{
|
||||
#ifdef _GLIB_ADDRESS_SANITIZER
|
||||
g_test_incomplete ("FIXME: Leaks lots of GCancellableSource objects, see glib#2309");
|
||||
(void) cancelled_cb;
|
||||
(void) threaded_dispose_thread_cb;
|
||||
#else
|
||||
ThreadedDisposeData data;
|
||||
GThread *thread = NULL;
|
||||
guint i;
|
||||
@ -276,6 +282,10 @@ test_cancellable_source_threaded_dispose (void)
|
||||
"(in one thread) and cancelling the GCancellable it refers "
|
||||
"to (in another thread)");
|
||||
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/1841");
|
||||
#ifdef _GLIB_ADDRESS_SANITIZER
|
||||
g_test_message ("We also ensure that no GCancellableSource are leaked");
|
||||
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2309");
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VALGRIND
|
||||
if (RUNNING_ON_VALGRIND)
|
||||
@ -341,7 +351,6 @@ test_cancellable_source_threaded_dispose (void)
|
||||
g_cond_clear (&data.cond);
|
||||
|
||||
g_ptr_array_unref (cancellables_pending_unref);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
@ -796,6 +805,528 @@ test_cancellable_cancel_reset_connect_races (void)
|
||||
g_object_unref (cancellable);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
source_cancelled_counter_cb (GCancellable *cancellable,
|
||||
gpointer user_data)
|
||||
{
|
||||
guint *n_calls = user_data;
|
||||
|
||||
*n_calls = *n_calls + 1;
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_cancellable_source_can_be_fired_multiple_times (void)
|
||||
{
|
||||
GCancellable *cancellable;
|
||||
GSource *source;
|
||||
guint n_calls = 0;
|
||||
|
||||
g_test_summary ("Test a cancellable source callback can be called multiple times");
|
||||
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/774");
|
||||
|
||||
cancellable = g_cancellable_new ();
|
||||
source = g_cancellable_source_new (cancellable);
|
||||
|
||||
g_source_set_callback (source, G_SOURCE_FUNC (source_cancelled_counter_cb),
|
||||
&n_calls, NULL);
|
||||
g_source_attach (source, NULL);
|
||||
|
||||
g_cancellable_cancel (cancellable);
|
||||
g_assert_cmpuint (n_calls, ==, 0);
|
||||
|
||||
while (g_main_context_pending (NULL))
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpuint (n_calls, ==, 1);
|
||||
|
||||
g_cancellable_cancel (cancellable);
|
||||
|
||||
g_timeout_add_once (100, g_free, NULL);
|
||||
while (g_main_context_pending (NULL))
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpuint (n_calls, ==, 1);
|
||||
|
||||
g_cancellable_reset (cancellable);
|
||||
g_cancellable_cancel (cancellable);
|
||||
g_assert_cmpuint (n_calls, ==, 1);
|
||||
|
||||
while (g_main_context_pending (NULL))
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpuint (n_calls, ==, 2);
|
||||
|
||||
g_source_unref (source);
|
||||
g_object_unref (cancellable);
|
||||
}
|
||||
|
||||
struct _BadCancellable
|
||||
{
|
||||
GCancellable parent;
|
||||
};
|
||||
|
||||
G_DECLARE_FINAL_TYPE (BadCancellable, bad_cancellable, BAD, CANCELLABLE, GCancellable);
|
||||
G_DEFINE_TYPE (BadCancellable, bad_cancellable, G_TYPE_CANCELLABLE)
|
||||
|
||||
static void
|
||||
bad_cancellable_cancel (GCancellable *cancellable)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
bad_cancellable_class_init (BadCancellableClass *klass)
|
||||
{
|
||||
G_CANCELLABLE_CLASS (klass)->cancel = bad_cancellable_cancel;
|
||||
}
|
||||
|
||||
static void
|
||||
bad_cancellable_init (BadCancellable *bad_cancellable)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
test_cancellable_wrong_cancel_override (void)
|
||||
{
|
||||
BadCancellable *bad = g_object_new (bad_cancellable_get_type (), NULL);
|
||||
|
||||
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
|
||||
"*Overridden cancel function*");
|
||||
g_cancellable_cancel (G_CANCELLABLE (bad));
|
||||
g_test_assert_expected_messages ();
|
||||
|
||||
g_assert_true (g_cancellable_is_cancelled (G_CANCELLABLE (bad)));
|
||||
|
||||
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[])
|
||||
{
|
||||
@ -810,7 +1341,22 @@ main (int argc, char *argv[])
|
||||
g_test_add_func ("/cancellable/poll-fd-cancelled-threaded", test_cancellable_cancelled_poll_fd_threaded);
|
||||
g_test_add_func ("/cancellable/cancel-reset-races", test_cancellable_cancel_reset_races);
|
||||
g_test_add_func ("/cancellable/cancel-reset-connect-races", test_cancellable_cancel_reset_connect_races);
|
||||
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 ();
|
||||
}
|
||||
|
@ -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) ||
|
||||
|
@ -1007,17 +1007,16 @@ test_dbus_roundtrip (void)
|
||||
static void
|
||||
test_dbus_peer_roundtrip (void)
|
||||
{
|
||||
#ifdef _GLIB_ADDRESS_SANITIZER
|
||||
g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313");
|
||||
(void) peer_connection_up;
|
||||
(void) peer_connection_down;
|
||||
#else
|
||||
PeerConnection peer;
|
||||
|
||||
#ifdef _GLIB_ADDRESS_SANITIZER
|
||||
g_test_message ("Ensure that no GCancellableSource are leaked");
|
||||
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2313");
|
||||
#endif
|
||||
|
||||
peer_connection_up (&peer);
|
||||
do_roundtrip (peer.server_connection, peer.client_connection);
|
||||
peer_connection_down (&peer);
|
||||
#endif
|
||||
}
|
||||
|
||||
static gint items_changed_count;
|
||||
@ -1146,17 +1145,16 @@ test_dbus_subscriptions (void)
|
||||
static void
|
||||
test_dbus_peer_subscriptions (void)
|
||||
{
|
||||
#ifdef _GLIB_ADDRESS_SANITIZER
|
||||
g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313");
|
||||
(void) peer_connection_up;
|
||||
(void) peer_connection_down;
|
||||
#else
|
||||
PeerConnection peer;
|
||||
|
||||
#ifdef _GLIB_ADDRESS_SANITIZER
|
||||
g_test_message ("Ensure that no GCancellableSource are leaked");
|
||||
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2313");
|
||||
#endif
|
||||
|
||||
peer_connection_up (&peer);
|
||||
do_subscriptions (peer.server_connection, peer.client_connection);
|
||||
peer_connection_down (&peer);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
|
Loading…
Reference in New Issue
Block a user