mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-11-04 01:58:54 +01:00 
			
		
		
		
	* 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
		
			
				
	
	
		
			225 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			225 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* 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 ();
 | 
						|
}
 |