From 54579bf88f2af4056fd19e539cc336bd17baea18 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Fri, 12 Aug 2011 11:49:31 +0200 Subject: [PATCH] gio: GCancellable can be used concurrently * Update documentation to note that GCancellable can be used concurrently by multiple operations. * Add documentation to g_cancellable_reset that behavior is undefined if called from within cancelled handler. * Add test for multiple concurrent operations using the same cancellable. https://bugzilla.gnome.org/show_bug.cgi?id=656387 --- gio/gcancellable.c | 7 +- gio/tests/.gitignore | 1 + gio/tests/Makefile.am | 3 + gio/tests/cancellable.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 gio/tests/cancellable.c diff --git a/gio/gcancellable.c b/gio/gcancellable.c index 8ed1b26..229f00c 100644 --- a/gio/gcancellable.c +++ b/gio/gcancellable.c @@ -171,7 +171,7 @@ g_cancellable_init (GCancellable *cancellable) * and pass it to the operations. * * One #GCancellable can be used in multiple consecutive - * operations, but not in multiple concurrent operations. + * operations or in multiple concurrent operations. * * Returns: a #GCancellable. **/ @@ -251,7 +251,10 @@ g_cancellable_get_current (void) * g_cancellable_reset: * @cancellable: a #GCancellable object. * - * Resets @cancellable to its uncancelled state. + * Resets @cancellable to its uncancelled state. + * + * If cancellable is currently in use by any cancellable operation + * then the behavior of this function is undefined. **/ void g_cancellable_reset (GCancellable *cancellable) diff --git a/gio/tests/.gitignore b/gio/tests/.gitignore index 7018b4b..9cdb9ad 100644 --- a/gio/tests/.gitignore +++ b/gio/tests/.gitignore @@ -4,6 +4,7 @@ appinfo-test async-close-output-stream buffered-input-stream buffered-output-stream +cancellable connectable contenttype contexts diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am index d27c68f..f188b1d 100644 --- a/gio/tests/Makefile.am +++ b/gio/tests/Makefile.am @@ -51,6 +51,7 @@ TEST_PROGS += \ socket \ pollable \ tls-certificate \ + cancellable \ $(NULL) if OS_UNIX @@ -460,6 +461,8 @@ proxy_LDADD = $(progs_ldadd) \ tls_certificate_SOURCES = tls-certificate.c gtesttlsbackend.c gtesttlsbackend.h tls_certificate_LDADD = $(progs_ldadd) +cancellable_LDADD = $(progs_ldadd) + # ----------------------------------------------------------------------------- if OS_UNIX diff --git a/gio/tests/cancellable.c b/gio/tests/cancellable.c new file mode 100644 index 0000000..e4fd098 --- /dev/null +++ b/gio/tests/cancellable.c @@ -0,0 +1,224 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2011 Collabora Ltd. + * + * 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 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, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Stef Walter + */ + +#include + +#include + +/* How long to wait in ms for each iteration */ +#define WAIT_ITERATION (10) + +static gint num_async_operations = 0; + +typedef struct +{ + guint iterations_requested; + guint iterations_done; + GCancellable *cancellable; +} MockOperationData; + +static void +mock_operation_free (gpointer user_data) +{ + MockOperationData *data = user_data; + g_object_unref (data->cancellable); + g_free (data); +} + +static void +mock_operation_thread (GSimpleAsyncResult *simple, + GObject *object, + GCancellable *cancellable) +{ + MockOperationData *data; + guint i; + + data = g_simple_async_result_get_op_res_gpointer (simple); + g_assert (data->cancellable == cancellable); + + for (i = 0; i < data->iterations_requested; i++) + { + if (g_cancellable_is_cancelled (data->cancellable)) + break; + if (g_test_verbose ()) + g_printerr ("THRD: %u iteration %u\n", data->iterations_requested, i); + g_usleep (WAIT_ITERATION * 1000); + } + + if (g_test_verbose ()) + g_printerr ("THRD: %u stopped at %u\n", data->iterations_requested, i); + data->iterations_done = i; +} + +static gboolean +mock_operation_timeout (gpointer user_data) +{ + GSimpleAsyncResult *simple; + MockOperationData *data; + GError *error = NULL; + gboolean done = FALSE; + + simple = G_SIMPLE_ASYNC_RESULT (user_data); + data = g_simple_async_result_get_op_res_gpointer (simple); + + if (data->iterations_done >= data->iterations_requested) + done = TRUE; + + if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) { + g_simple_async_result_take_error (simple, error); + done = TRUE; + } + + if (done) { + if (g_test_verbose ()) + g_printerr ("LOOP: %u stopped at %u\n", data->iterations_requested,\ + data->iterations_done); + g_simple_async_result_complete (simple); + return FALSE; /* don't call timeout again */ + + } else { + data->iterations_done++; + if (g_test_verbose ()) + g_printerr ("LOOP: %u iteration %u\n", data->iterations_requested, + data->iterations_done); + return TRUE; /* call timeout */ + } +} + +static void +mock_operation_async (guint wait_iterations, + gboolean run_in_thread, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + MockOperationData *data; + + simple = g_simple_async_result_new (NULL, callback, user_data, + mock_operation_async); + data = g_new0 (MockOperationData, 1); + data->iterations_requested = wait_iterations; + data->cancellable = g_object_ref (cancellable); + g_simple_async_result_set_op_res_gpointer (simple, data, mock_operation_free); + + if (run_in_thread) { + g_simple_async_result_run_in_thread (simple, mock_operation_thread, + G_PRIORITY_DEFAULT, cancellable); + if (g_test_verbose ()) + g_printerr ("THRD: %d started\n", wait_iterations); + } else { + g_timeout_add_full (G_PRIORITY_DEFAULT, WAIT_ITERATION, mock_operation_timeout, + g_object_ref (simple), g_object_unref); + if (g_test_verbose ()) + g_printerr ("LOOP: %d started\n", wait_iterations); + } + + g_object_unref (simple); +} + +static guint +mock_operation_finish (GAsyncResult *result, + GError **error) +{ + MockOperationData *data; + + g_assert (g_simple_async_result_is_valid (result, NULL, mock_operation_async)); + g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error); + + data = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)); + return data->iterations_done; +} + +static void +on_mock_operation_ready (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + guint iterations_requested; + guint iterations_done; + GError *error = NULL; + + iterations_requested = GPOINTER_TO_UINT (user_data); + iterations_done = mock_operation_finish (result, &error); + + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_error_free (error); + + g_assert_cmpint (iterations_requested, >, iterations_done); + num_async_operations--; +} + +static gboolean +on_main_loop_timeout_quit (gpointer user_data) +{ + GMainLoop *loop = user_data; + g_main_loop_quit (loop); + return FALSE; +} + +static void +test_cancel_multiple_concurrent (void) +{ + GCancellable *cancellable; + guint i, iterations; + GMainLoop *loop; + + cancellable = g_cancellable_new (); + loop = g_main_loop_new (NULL, FALSE); + + for (i = 0; i < 45; i++) + { + iterations = i + 10; + mock_operation_async (iterations, g_random_boolean (), cancellable, + on_mock_operation_ready, GUINT_TO_POINTER (iterations)); + num_async_operations++; + } + + /* Wait for two iterations, to give threads a chance to start up */ + g_timeout_add (WAIT_ITERATION * 2, on_main_loop_timeout_quit, loop); + g_main_loop_run (loop); + g_assert_cmpint (num_async_operations, ==, 45); + if (g_test_verbose ()) + g_printerr ("CANCEL: %d operations\n", num_async_operations); + g_cancellable_cancel (cancellable); + g_assert (g_cancellable_is_cancelled (cancellable)); + + /* Wait for two more iterations, and all threads should be cancelled */ + g_timeout_add (WAIT_ITERATION * 2, on_main_loop_timeout_quit, loop); + g_main_loop_run (loop); + g_assert_cmpint (num_async_operations, ==, 0); + + g_object_unref (cancellable); + g_main_loop_unref (loop); +} + +int +main (int argc, char *argv[]) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/cancellable/multiple-concurrent", test_cancel_multiple_concurrent); + + return g_test_run (); +} -- 2.7.4