[glib: 1/2] gio: GCancellable can be used concurrently



commit 54579bf88f2af4056fd19e539cc336bd17baea18
Author: Stef Walter <stefw collabora co uk>
Date:   Fri Aug 12 11:49:31 2011 +0200

    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(-)
---
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 <stefw collabora co uk>
+ */
+
+#include <locale.h>
+
+#include <gio/gio.h>
+
+/* 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 ();
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]