mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-10-18 19:32:52 +02:00
My application (OSTree) has suffered really bad performance degredation recently, and doing some analysis of the problem, I think a lot stems from the recent order of magnitude increase in the default GLib worker threads. OSTree does a *lot* of async I/O (directory enumeration, calling link(), reading/writing files) etc. It very quickly reaches 100 threads, but there is up/down thread churn as threads complete, but process gets I/O bound and thus the threads are starved of work, but then the main thread manages to fill the queue more, suddenly spinning up lots of worker threads again. Now, as this patch forces users to specify, there are several distinct classes of tasks. It basically never makes sense to run more CPU-bound threads than there are CPUs. Spawning 90+ threads to do SHA256 calculation for example is just creating pointless extra contention on a dual-processor system. Therefore, the limit of the thread pool for G_TASK_THREAD_KIND_CPU is the number of processors. Similarly, there's a real limit to how much I/O traffic it makes sense to schedule simultaneously. I need to do more research here - what makes sense for my laptop with 1 magnetic hard drive is likely different from a fast server with RAID on top of SSDs. For the moment, I've chosen to limit I/O bound threads to 4. The limit for _DEFAULT remains 100 - but we can solve this better if we have e.g. a way for tasks to express dependencies, among other things. But for now, with this patch, and OSTree modified to use G_TASK_THREAD_KIND_IO for its rename()/link() threads, I am seeing better performance. https://bugzilla.gnome.org/show_bug.cgi?id=687223
1685 lines
45 KiB
C
1685 lines
45 KiB
C
/*
|
|
* Copyright 2012 Red Hat, Inc.
|
|
*
|
|
* This program 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 licence or (at
|
|
* your option) any later version.
|
|
*
|
|
* See the included COPYING file for more information.
|
|
*/
|
|
|
|
#include <gio/gio.h>
|
|
|
|
static GMainLoop *loop;
|
|
static GThread *main_thread;
|
|
static gssize magic;
|
|
|
|
/* We need objects for a few tests where we don't care what type
|
|
* they are, just that they're GObjects.
|
|
*/
|
|
#define g_dummy_object_new g_socket_client_new
|
|
|
|
/* test_basic */
|
|
|
|
static void
|
|
basic_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gssize *result_out = user_data;
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (!g_task_had_error (G_TASK (result)));
|
|
|
|
*result_out = g_task_propagate_int (G_TASK (result), &error);
|
|
g_assert_no_error (error);
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static gboolean
|
|
basic_return (gpointer user_data)
|
|
{
|
|
GTask *task = user_data;
|
|
|
|
g_task_return_int (task, magic);
|
|
g_object_unref (task);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
basic_destroy_notify (gpointer user_data)
|
|
{
|
|
gboolean *destroyed = user_data;
|
|
|
|
*destroyed = TRUE;
|
|
}
|
|
|
|
static void
|
|
test_basic (void)
|
|
{
|
|
GTask *task;
|
|
gssize result;
|
|
gboolean task_data_destroyed = FALSE;
|
|
|
|
task = g_task_new (NULL, NULL, basic_callback, &result);
|
|
g_task_set_task_data (task, &task_data_destroyed, basic_destroy_notify);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
|
|
g_idle_add (basic_return, task);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (result, ==, magic);
|
|
g_assert (task_data_destroyed == TRUE);
|
|
g_assert (task == NULL);
|
|
}
|
|
|
|
/* test_error */
|
|
|
|
static void
|
|
error_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gssize *result_out = user_data;
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (g_task_had_error (G_TASK (result)));
|
|
|
|
*result_out = g_task_propagate_int (G_TASK (result), &error);
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
|
|
g_error_free (error);
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static gboolean
|
|
error_return (gpointer user_data)
|
|
{
|
|
GTask *task = user_data;
|
|
|
|
g_task_return_new_error (task,
|
|
G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed");
|
|
g_object_unref (task);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
error_destroy_notify (gpointer user_data)
|
|
{
|
|
gboolean *destroyed = user_data;
|
|
|
|
*destroyed = TRUE;
|
|
}
|
|
|
|
static void
|
|
test_error (void)
|
|
{
|
|
GTask *task;
|
|
gssize result;
|
|
gboolean first_task_data_destroyed = FALSE;
|
|
gboolean second_task_data_destroyed = FALSE;
|
|
|
|
task = g_task_new (NULL, NULL, error_callback, &result);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
|
|
g_assert (first_task_data_destroyed == FALSE);
|
|
g_task_set_task_data (task, &first_task_data_destroyed, error_destroy_notify);
|
|
g_assert (first_task_data_destroyed == FALSE);
|
|
|
|
/* Calling g_task_set_task_data() again will destroy the first data */
|
|
g_task_set_task_data (task, &second_task_data_destroyed, error_destroy_notify);
|
|
g_assert (first_task_data_destroyed == TRUE);
|
|
g_assert (second_task_data_destroyed == FALSE);
|
|
|
|
g_idle_add (error_return, task);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (result, ==, -1);
|
|
g_assert (second_task_data_destroyed == TRUE);
|
|
g_assert (task == NULL);
|
|
}
|
|
|
|
/* test_return_from_same_iteration: calling g_task_return_* from the
|
|
* loop iteration the task was created in defers completion until the
|
|
* next iteration.
|
|
*/
|
|
gboolean same_result = FALSE;
|
|
|
|
static void
|
|
same_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gboolean *result_out = user_data;
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (!g_task_had_error (G_TASK (result)));
|
|
|
|
*result_out = g_task_propagate_boolean (G_TASK (result), &error);
|
|
g_assert_no_error (error);
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static gboolean
|
|
same_start (gpointer user_data)
|
|
{
|
|
gpointer *weak_pointer = user_data;
|
|
GTask *task;
|
|
|
|
task = g_task_new (NULL, NULL, same_callback, &same_result);
|
|
*weak_pointer = task;
|
|
g_object_add_weak_pointer (G_OBJECT (task), weak_pointer);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
/* same_callback should not have been invoked yet */
|
|
g_assert (same_result == FALSE);
|
|
g_assert (*weak_pointer == task);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
test_return_from_same_iteration (void)
|
|
{
|
|
gpointer weak_pointer;
|
|
|
|
g_idle_add (same_start, &weak_pointer);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert (same_result == TRUE);
|
|
g_assert (weak_pointer == NULL);
|
|
}
|
|
|
|
/* test_return_from_toplevel: calling g_task_return_* from outside any
|
|
* main loop completes the task inside the main loop.
|
|
*/
|
|
|
|
static void
|
|
toplevel_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gboolean *result_out = user_data;
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (!g_task_had_error (G_TASK (result)));
|
|
|
|
*result_out = g_task_propagate_boolean (G_TASK (result), &error);
|
|
g_assert_no_error (error);
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
test_return_from_toplevel (void)
|
|
{
|
|
GTask *task;
|
|
gboolean result = FALSE;
|
|
|
|
task = g_task_new (NULL, NULL, toplevel_callback, &result);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
/* toplevel_callback should not have been invoked yet */
|
|
g_assert (result == FALSE);
|
|
g_assert (task != NULL);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert (result == TRUE);
|
|
g_assert (task == NULL);
|
|
}
|
|
|
|
/* test_return_from_anon_thread: calling g_task_return_* from a
|
|
* thread with no thread-default main context will complete the
|
|
* task in the task's context/thread.
|
|
*/
|
|
|
|
GThread *anon_thread;
|
|
|
|
static void
|
|
anon_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gssize *result_out = user_data;
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (!g_task_had_error (G_TASK (result)));
|
|
|
|
g_assert (g_thread_self () == main_thread);
|
|
|
|
*result_out = g_task_propagate_int (G_TASK (result), &error);
|
|
g_assert_no_error (error);
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static gpointer
|
|
anon_thread_func (gpointer user_data)
|
|
{
|
|
GTask *task = user_data;
|
|
|
|
g_task_return_int (task, magic);
|
|
g_object_unref (task);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
anon_start (gpointer user_data)
|
|
{
|
|
GTask *task = user_data;
|
|
|
|
anon_thread = g_thread_new ("test_return_from_anon_thread",
|
|
anon_thread_func, task);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
test_return_from_anon_thread (void)
|
|
{
|
|
GTask *task;
|
|
gssize result = 0;
|
|
|
|
task = g_task_new (NULL, NULL, anon_callback, &result);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
|
|
g_idle_add (anon_start, task);
|
|
g_main_loop_run (loop);
|
|
|
|
g_thread_join (anon_thread);
|
|
|
|
g_assert_cmpint (result, ==, magic);
|
|
g_assert (task == NULL);
|
|
}
|
|
|
|
/* test_return_from_wrong_thread: calling g_task_return_* from a
|
|
* thread with its own thread-default main context will complete the
|
|
* task in the task's context/thread.
|
|
*/
|
|
|
|
GThread *wrong_thread;
|
|
|
|
static void
|
|
wrong_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gssize *result_out = user_data;
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (!g_task_had_error (G_TASK (result)));
|
|
|
|
g_assert (g_thread_self () == main_thread);
|
|
|
|
*result_out = g_task_propagate_int (G_TASK (result), &error);
|
|
g_assert_no_error (error);
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static gpointer
|
|
wrong_thread_func (gpointer user_data)
|
|
{
|
|
GTask *task = user_data;
|
|
GMainContext *context;
|
|
|
|
context = g_main_context_new ();
|
|
g_main_context_push_thread_default (context);
|
|
|
|
g_assert (g_task_get_context (task) != context);
|
|
|
|
g_task_return_int (task, magic);
|
|
g_object_unref (task);
|
|
|
|
g_main_context_pop_thread_default (context);
|
|
g_main_context_unref (context);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
wrong_start (gpointer user_data)
|
|
{
|
|
GTask *task = user_data;
|
|
|
|
wrong_thread = g_thread_new ("test_return_from_anon_thread",
|
|
wrong_thread_func, task);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
test_return_from_wrong_thread (void)
|
|
{
|
|
GTask *task;
|
|
gssize result = 0;
|
|
|
|
task = g_task_new (NULL, NULL, wrong_callback, &result);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
|
|
g_idle_add (wrong_start, task);
|
|
g_main_loop_run (loop);
|
|
|
|
g_thread_join (wrong_thread);
|
|
|
|
g_assert_cmpint (result, ==, magic);
|
|
g_assert (task == NULL);
|
|
}
|
|
|
|
/* test_no_callback */
|
|
|
|
static void
|
|
test_no_callback (void)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (NULL, NULL, NULL, NULL);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
/* Since there's no callback, g_task_return_boolean() will
|
|
* not have queued an idle source and taken a ref on task,
|
|
* so we just dropped the last ref.
|
|
*/
|
|
g_assert (task == NULL);
|
|
}
|
|
|
|
/* test_report_error */
|
|
|
|
static void test_report_error (void);
|
|
|
|
static void
|
|
report_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gpointer *weak_pointer = user_data;
|
|
GError *error = NULL;
|
|
gssize ret;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (g_async_result_is_tagged (result, test_report_error));
|
|
g_assert (g_task_get_source_tag (G_TASK (result)) == test_report_error);
|
|
g_assert (g_task_had_error (G_TASK (result)));
|
|
|
|
ret = g_task_propagate_int (G_TASK (result), &error);
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
|
|
g_assert_cmpint (ret, ==, -1);
|
|
g_error_free (error);
|
|
|
|
*weak_pointer = result;
|
|
g_object_add_weak_pointer (G_OBJECT (result), weak_pointer);
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
test_report_error (void)
|
|
{
|
|
gpointer weak_pointer = (gpointer)-1;
|
|
|
|
g_task_report_new_error (NULL, report_callback, &weak_pointer,
|
|
test_report_error,
|
|
G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed");
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert (weak_pointer == NULL);
|
|
}
|
|
|
|
/* test_priority: tasks complete in priority order */
|
|
|
|
static int counter = 0;
|
|
|
|
static void
|
|
priority_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gssize *ret_out = user_data;
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (!g_task_had_error (G_TASK (result)));
|
|
|
|
g_task_propagate_boolean (G_TASK (result), &error);
|
|
g_assert_no_error (error);
|
|
|
|
*ret_out = ++counter;
|
|
|
|
if (counter == 3)
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
test_priority (void)
|
|
{
|
|
GTask *t1, *t2, *t3;
|
|
gssize ret1, ret2, ret3;
|
|
|
|
/* t2 has higher priority than either t1 or t3, so we can't
|
|
* accidentally pass the test just by completing the tasks in the
|
|
* order they were created (or in reverse order).
|
|
*/
|
|
|
|
t1 = g_task_new (NULL, NULL, priority_callback, &ret1);
|
|
g_task_set_priority (t1, G_PRIORITY_DEFAULT);
|
|
g_task_return_boolean (t1, TRUE);
|
|
g_object_unref (t1);
|
|
|
|
t2 = g_task_new (NULL, NULL, priority_callback, &ret2);
|
|
g_task_set_priority (t2, G_PRIORITY_HIGH);
|
|
g_task_return_boolean (t2, TRUE);
|
|
g_object_unref (t2);
|
|
|
|
t3 = g_task_new (NULL, NULL, priority_callback, &ret3);
|
|
g_task_set_priority (t3, G_PRIORITY_LOW);
|
|
g_task_return_boolean (t3, TRUE);
|
|
g_object_unref (t3);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (ret2, ==, 1);
|
|
g_assert_cmpint (ret1, ==, 2);
|
|
g_assert_cmpint (ret3, ==, 3);
|
|
}
|
|
|
|
/* test_check_cancellable: cancellation overrides return value */
|
|
|
|
enum {
|
|
CANCEL_BEFORE = (1 << 1),
|
|
CANCEL_AFTER = (1 << 2),
|
|
CHECK_CANCELLABLE = (1 << 3)
|
|
};
|
|
#define NUM_CANCEL_TESTS (CANCEL_BEFORE | CANCEL_AFTER | CHECK_CANCELLABLE)
|
|
|
|
static void
|
|
cancel_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
int state = GPOINTER_TO_INT (user_data);
|
|
GTask *task;
|
|
GCancellable *cancellable;
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
|
|
task = G_TASK (result);
|
|
cancellable = g_task_get_cancellable (task);
|
|
g_assert (G_IS_CANCELLABLE (cancellable));
|
|
|
|
if (state & (CANCEL_BEFORE | CANCEL_AFTER))
|
|
g_assert (g_cancellable_is_cancelled (cancellable));
|
|
else
|
|
g_assert (!g_cancellable_is_cancelled (cancellable));
|
|
|
|
if (state & CHECK_CANCELLABLE)
|
|
g_assert (g_task_get_check_cancellable (task));
|
|
else
|
|
g_assert (!g_task_get_check_cancellable (task));
|
|
|
|
if (g_task_propagate_boolean (task, &error))
|
|
{
|
|
g_assert (!g_cancellable_is_cancelled (cancellable) ||
|
|
!g_task_get_check_cancellable (task));
|
|
}
|
|
else
|
|
{
|
|
g_assert (g_cancellable_is_cancelled (cancellable) &&
|
|
g_task_get_check_cancellable (task));
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
test_check_cancellable (void)
|
|
{
|
|
GTask *task;
|
|
GCancellable *cancellable;
|
|
int state;
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
for (state = 0; state <= NUM_CANCEL_TESTS; state++)
|
|
{
|
|
task = g_task_new (NULL, cancellable, cancel_callback,
|
|
GINT_TO_POINTER (state));
|
|
g_task_set_check_cancellable (task, (state & CHECK_CANCELLABLE) != 0);
|
|
|
|
if (state & CANCEL_BEFORE)
|
|
g_cancellable_cancel (cancellable);
|
|
g_task_return_boolean (task, TRUE);
|
|
if (state & CANCEL_AFTER)
|
|
g_cancellable_cancel (cancellable);
|
|
|
|
g_main_loop_run (loop);
|
|
g_object_unref (task);
|
|
g_cancellable_reset (cancellable);
|
|
}
|
|
|
|
g_object_unref (cancellable);
|
|
}
|
|
|
|
/* test_return_if_cancelled */
|
|
|
|
static void
|
|
return_if_cancelled_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (g_task_had_error (G_TASK (result)));
|
|
|
|
g_task_propagate_boolean (G_TASK (result), &error);
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_clear_error (&error);
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
test_return_if_cancelled (void)
|
|
{
|
|
GTask *task;
|
|
GCancellable *cancellable;
|
|
gboolean cancelled;
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
task = g_task_new (NULL, cancellable, return_if_cancelled_callback, NULL);
|
|
g_cancellable_cancel (cancellable);
|
|
cancelled = g_task_return_error_if_cancelled (task);
|
|
g_assert (cancelled);
|
|
g_main_loop_run (loop);
|
|
g_object_unref (task);
|
|
g_cancellable_reset (cancellable);
|
|
|
|
task = g_task_new (NULL, cancellable, return_if_cancelled_callback, NULL);
|
|
g_task_set_check_cancellable (task, FALSE);
|
|
g_cancellable_cancel (cancellable);
|
|
cancelled = g_task_return_error_if_cancelled (task);
|
|
g_assert (cancelled);
|
|
g_main_loop_run (loop);
|
|
g_object_unref (task);
|
|
g_object_unref (cancellable);
|
|
}
|
|
|
|
/* test_run_in_thread */
|
|
|
|
static void
|
|
run_in_thread_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gboolean *done = user_data;
|
|
GError *error = NULL;
|
|
gssize ret;
|
|
|
|
g_assert (g_thread_self () == main_thread);
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (!g_task_had_error (G_TASK (result)));
|
|
|
|
ret = g_task_propagate_int (G_TASK (result), &error);
|
|
g_assert_no_error (error);
|
|
g_assert_cmpint (ret, ==, magic);
|
|
|
|
*done = TRUE;
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
run_in_thread_thread (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
gboolean *thread_ran = task_data;
|
|
|
|
g_assert (source_object == g_task_get_source_object (task));
|
|
g_assert (task_data == g_task_get_task_data (task));
|
|
g_assert (cancellable == g_task_get_cancellable (task));
|
|
|
|
g_assert (g_thread_self () != main_thread);
|
|
|
|
*thread_ran = TRUE;
|
|
g_task_return_int (task, magic);
|
|
}
|
|
|
|
static void
|
|
test_run_in_thread (void)
|
|
{
|
|
GTask *task;
|
|
volatile gboolean thread_ran = FALSE;
|
|
gboolean done = FALSE;
|
|
|
|
task = g_task_new (NULL, NULL, run_in_thread_callback, &done);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
|
|
g_task_set_task_data (task, (gpointer)&thread_ran, NULL);
|
|
g_task_run_in_thread (task, run_in_thread_thread);
|
|
g_object_unref (task);
|
|
|
|
while (!thread_ran)
|
|
g_usleep (100);
|
|
|
|
g_assert (done == FALSE);
|
|
g_assert (task != NULL);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert (done == TRUE);
|
|
g_assert (task == NULL);
|
|
}
|
|
|
|
/* test_run_in_thread_sync */
|
|
|
|
static void
|
|
run_in_thread_sync_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
/* g_task_run_in_thread_sync() does not invoke the task's callback */
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
run_in_thread_sync_thread (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
gboolean *thread_ran = task_data;
|
|
|
|
g_assert (source_object == g_task_get_source_object (task));
|
|
g_assert (task_data == g_task_get_task_data (task));
|
|
g_assert (cancellable == g_task_get_cancellable (task));
|
|
|
|
g_assert (g_thread_self () != main_thread);
|
|
|
|
*thread_ran = TRUE;
|
|
g_task_return_int (task, magic);
|
|
}
|
|
|
|
static void
|
|
test_run_in_thread_sync (void)
|
|
{
|
|
GTask *task;
|
|
gboolean thread_ran = FALSE;
|
|
gssize ret;
|
|
GError *error = NULL;
|
|
|
|
task = g_task_new (NULL, NULL, run_in_thread_sync_callback, NULL);
|
|
|
|
g_task_set_task_data (task, &thread_ran, NULL);
|
|
g_task_run_in_thread_sync (task, run_in_thread_sync_thread);
|
|
|
|
g_assert (thread_ran == TRUE);
|
|
g_assert (task != NULL);
|
|
g_assert (!g_task_had_error (task));
|
|
|
|
ret = g_task_propagate_int (task, &error);
|
|
g_assert_no_error (error);
|
|
g_assert_cmpint (ret, ==, magic);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/* test_run_in_thread_priority */
|
|
|
|
static GMutex fake_task_mutex, last_fake_task_mutex;
|
|
static gint sequence_number = 0;
|
|
|
|
static void
|
|
quit_main_loop_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
gboolean ret;
|
|
|
|
g_assert (g_thread_self () == main_thread);
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (!g_task_had_error (G_TASK (result)));
|
|
|
|
ret = g_task_propagate_boolean (G_TASK (result), &error);
|
|
g_assert_no_error (error);
|
|
g_assert_cmpint (ret, ==, TRUE);
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
set_sequence_number_thread (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
gint *seq_no_p = task_data;
|
|
|
|
*seq_no_p = ++sequence_number;
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
static void
|
|
fake_task_thread (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
GMutex *mutex = task_data;
|
|
|
|
g_mutex_lock (mutex);
|
|
g_mutex_unlock (mutex);
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
#define G_TASK_THREAD_POOL_SIZE 100
|
|
|
|
static void
|
|
test_run_in_thread_priority (void)
|
|
{
|
|
GTask *task;
|
|
GCancellable *cancellable;
|
|
int seq_a, seq_b, seq_c, seq_d;
|
|
int i;
|
|
|
|
/* Flush the thread pool, then clog it up with junk tasks */
|
|
g_thread_pool_stop_unused_threads ();
|
|
|
|
g_mutex_lock (&fake_task_mutex);
|
|
for (i = 0; i < G_TASK_THREAD_POOL_SIZE - 1; i++)
|
|
{
|
|
task = g_task_new (NULL, NULL, NULL, NULL);
|
|
g_task_set_task_data (task, &fake_task_mutex, NULL);
|
|
g_assert_cmpint (g_task_get_priority (task), ==, G_PRIORITY_DEFAULT);
|
|
g_task_set_priority (task, G_PRIORITY_HIGH * 2);
|
|
g_assert_cmpint (g_task_get_priority (task), ==, G_PRIORITY_HIGH * 2);
|
|
g_task_run_in_thread (task, fake_task_thread);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
g_mutex_lock (&last_fake_task_mutex);
|
|
task = g_task_new (NULL, NULL, NULL, NULL);
|
|
g_task_set_task_data (task, &last_fake_task_mutex, NULL);
|
|
g_task_set_priority (task, G_PRIORITY_HIGH * 2);
|
|
g_task_run_in_thread (task, fake_task_thread);
|
|
g_object_unref (task);
|
|
|
|
/* Queue three more tasks that we'll arrange to have run serially */
|
|
task = g_task_new (NULL, NULL, NULL, NULL);
|
|
g_task_set_task_data (task, &seq_a, NULL);
|
|
g_task_run_in_thread (task, set_sequence_number_thread);
|
|
g_object_unref (task);
|
|
|
|
task = g_task_new (NULL, NULL, quit_main_loop_callback, NULL);
|
|
g_task_set_task_data (task, &seq_b, NULL);
|
|
g_task_set_priority (task, G_PRIORITY_LOW);
|
|
g_task_run_in_thread (task, set_sequence_number_thread);
|
|
g_object_unref (task);
|
|
|
|
task = g_task_new (NULL, NULL, NULL, NULL);
|
|
g_task_set_task_data (task, &seq_c, NULL);
|
|
g_task_set_priority (task, G_PRIORITY_HIGH);
|
|
g_task_run_in_thread (task, set_sequence_number_thread);
|
|
g_object_unref (task);
|
|
|
|
cancellable = g_cancellable_new ();
|
|
task = g_task_new (NULL, cancellable, NULL, NULL);
|
|
g_task_set_task_data (task, &seq_d, NULL);
|
|
g_task_run_in_thread (task, set_sequence_number_thread);
|
|
g_cancellable_cancel (cancellable);
|
|
g_object_unref (cancellable);
|
|
g_object_unref (task);
|
|
|
|
/* Let the last fake task complete; the four other tasks will then
|
|
* complete serially, in the order D, C, A, B, and B will quit the
|
|
* main loop.
|
|
*/
|
|
g_mutex_unlock (&last_fake_task_mutex);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (seq_d, ==, 1);
|
|
g_assert_cmpint (seq_c, ==, 2);
|
|
g_assert_cmpint (seq_a, ==, 3);
|
|
g_assert_cmpint (seq_b, ==, 4);
|
|
|
|
g_mutex_unlock (&fake_task_mutex);
|
|
}
|
|
|
|
static void
|
|
test_run_in_thread_kind (void)
|
|
{
|
|
GTask *task;
|
|
gint seq = 42;
|
|
|
|
/* No real semantics here, we're basically just calling
|
|
* g_task_set_scheduling() to get basic code coverage.
|
|
*/
|
|
task = g_task_new (NULL, NULL, quit_main_loop_callback, NULL);
|
|
g_task_set_task_data (task, &seq, NULL);
|
|
g_task_set_scheduling (task, G_PRIORITY_DEFAULT, G_TASK_THREAD_KIND_IO);
|
|
g_task_run_in_thread (task, set_sequence_number_thread);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/* test_return_on_cancel */
|
|
|
|
GMutex roc_init_mutex, roc_finish_mutex;
|
|
GCond roc_init_cond, roc_finish_cond;
|
|
|
|
typedef enum {
|
|
THREAD_STARTING,
|
|
THREAD_RUNNING,
|
|
THREAD_CANCELLED,
|
|
THREAD_COMPLETED
|
|
} ThreadState;
|
|
|
|
static void
|
|
return_on_cancel_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gboolean *callback_ran = user_data;
|
|
GError *error = NULL;
|
|
gssize ret;
|
|
|
|
g_assert (g_thread_self () == main_thread);
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (g_task_had_error (G_TASK (result)));
|
|
|
|
ret = g_task_propagate_int (G_TASK (result), &error);
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_clear_error (&error);
|
|
g_assert_cmpint (ret, ==, -1);
|
|
|
|
*callback_ran = TRUE;
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
return_on_cancel_thread (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
ThreadState *state = task_data;
|
|
|
|
g_assert (source_object == g_task_get_source_object (task));
|
|
g_assert (task_data == g_task_get_task_data (task));
|
|
g_assert (cancellable == g_task_get_cancellable (task));
|
|
|
|
g_assert (g_thread_self () != main_thread);
|
|
|
|
g_mutex_lock (&roc_init_mutex);
|
|
*state = THREAD_RUNNING;
|
|
g_cond_signal (&roc_init_cond);
|
|
g_mutex_unlock (&roc_init_mutex);
|
|
|
|
g_mutex_lock (&roc_finish_mutex);
|
|
|
|
if (!g_task_get_return_on_cancel (task) ||
|
|
g_task_set_return_on_cancel (task, FALSE))
|
|
{
|
|
*state = THREAD_COMPLETED;
|
|
g_task_return_int (task, magic);
|
|
}
|
|
else
|
|
*state = THREAD_CANCELLED;
|
|
|
|
g_cond_signal (&roc_finish_cond);
|
|
g_mutex_unlock (&roc_finish_mutex);
|
|
}
|
|
|
|
static void
|
|
test_return_on_cancel (void)
|
|
{
|
|
GTask *task;
|
|
GCancellable *cancellable;
|
|
volatile ThreadState thread_state;
|
|
gboolean callback_ran;
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
/* If return-on-cancel is FALSE (default), the task does not return
|
|
* early.
|
|
*/
|
|
callback_ran = FALSE;
|
|
thread_state = THREAD_STARTING;
|
|
task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
|
|
|
|
g_task_set_task_data (task, (gpointer)&thread_state, NULL);
|
|
g_mutex_lock (&roc_init_mutex);
|
|
g_mutex_lock (&roc_finish_mutex);
|
|
g_task_run_in_thread (task, return_on_cancel_thread);
|
|
g_object_unref (task);
|
|
|
|
while (thread_state == THREAD_STARTING)
|
|
g_cond_wait (&roc_init_cond, &roc_init_mutex);
|
|
g_mutex_unlock (&roc_init_mutex);
|
|
|
|
g_assert (thread_state == THREAD_RUNNING);
|
|
g_assert (callback_ran == FALSE);
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
g_mutex_unlock (&roc_finish_mutex);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert (thread_state == THREAD_COMPLETED);
|
|
g_assert (callback_ran == TRUE);
|
|
|
|
g_cancellable_reset (cancellable);
|
|
|
|
/* If return-on-cancel is TRUE, it does return early */
|
|
callback_ran = FALSE;
|
|
thread_state = THREAD_STARTING;
|
|
task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
|
|
g_task_set_return_on_cancel (task, TRUE);
|
|
|
|
g_task_set_task_data (task, (gpointer)&thread_state, NULL);
|
|
g_mutex_lock (&roc_init_mutex);
|
|
g_mutex_lock (&roc_finish_mutex);
|
|
g_task_run_in_thread (task, return_on_cancel_thread);
|
|
g_object_unref (task);
|
|
|
|
while (thread_state == THREAD_STARTING)
|
|
g_cond_wait (&roc_init_cond, &roc_init_mutex);
|
|
g_mutex_unlock (&roc_init_mutex);
|
|
|
|
g_assert (thread_state == THREAD_RUNNING);
|
|
g_assert (callback_ran == FALSE);
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
g_main_loop_run (loop);
|
|
g_assert (thread_state == THREAD_RUNNING);
|
|
g_assert (callback_ran == TRUE);
|
|
|
|
while (thread_state == THREAD_RUNNING)
|
|
g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
|
|
g_mutex_unlock (&roc_finish_mutex);
|
|
|
|
g_assert (thread_state == THREAD_CANCELLED);
|
|
/* We can't g_assert (task == NULL) here because it won't become NULL
|
|
* until a little bit after roc_finish_cond is signaled.
|
|
*/
|
|
|
|
g_cancellable_reset (cancellable);
|
|
|
|
/* If the task is already cancelled before it starts, it returns
|
|
* immediately, but the thread func still runs.
|
|
*/
|
|
callback_ran = FALSE;
|
|
thread_state = THREAD_STARTING;
|
|
task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
|
|
g_task_set_return_on_cancel (task, TRUE);
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
|
|
g_task_set_task_data (task, (gpointer)&thread_state, NULL);
|
|
g_mutex_lock (&roc_init_mutex);
|
|
g_mutex_lock (&roc_finish_mutex);
|
|
g_task_run_in_thread (task, return_on_cancel_thread);
|
|
g_object_unref (task);
|
|
|
|
g_main_loop_run (loop);
|
|
g_assert (callback_ran == TRUE);
|
|
|
|
while (thread_state == THREAD_STARTING)
|
|
g_cond_wait (&roc_init_cond, &roc_init_mutex);
|
|
g_mutex_unlock (&roc_init_mutex);
|
|
|
|
g_assert (thread_state == THREAD_RUNNING);
|
|
|
|
while (thread_state == THREAD_RUNNING)
|
|
g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
|
|
g_mutex_unlock (&roc_finish_mutex);
|
|
|
|
g_assert (thread_state == THREAD_CANCELLED);
|
|
|
|
g_object_unref (cancellable);
|
|
}
|
|
|
|
/* test_return_on_cancel_sync */
|
|
|
|
static gpointer
|
|
cancel_sync_runner_thread (gpointer task)
|
|
{
|
|
g_task_run_in_thread_sync (task, return_on_cancel_thread);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
test_return_on_cancel_sync (void)
|
|
{
|
|
GTask *task;
|
|
GCancellable *cancellable;
|
|
volatile ThreadState thread_state;
|
|
GThread *runner_thread;
|
|
gssize ret;
|
|
GError *error = NULL;
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
/* If return-on-cancel is FALSE, the task does not return early.
|
|
*/
|
|
thread_state = THREAD_STARTING;
|
|
task = g_task_new (NULL, cancellable, run_in_thread_sync_callback, NULL);
|
|
|
|
g_task_set_task_data (task, (gpointer)&thread_state, NULL);
|
|
g_mutex_lock (&roc_init_mutex);
|
|
g_mutex_lock (&roc_finish_mutex);
|
|
runner_thread = g_thread_new ("return-on-cancel-sync runner thread",
|
|
cancel_sync_runner_thread, task);
|
|
|
|
while (thread_state == THREAD_STARTING)
|
|
g_cond_wait (&roc_init_cond, &roc_init_mutex);
|
|
g_mutex_unlock (&roc_init_mutex);
|
|
|
|
g_assert (thread_state == THREAD_RUNNING);
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
g_mutex_unlock (&roc_finish_mutex);
|
|
g_thread_join (runner_thread);
|
|
g_assert (thread_state == THREAD_COMPLETED);
|
|
|
|
ret = g_task_propagate_int (task, &error);
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_clear_error (&error);
|
|
g_assert_cmpint (ret, ==, -1);
|
|
|
|
g_object_unref (task);
|
|
|
|
g_cancellable_reset (cancellable);
|
|
|
|
/* If return-on-cancel is TRUE, it does return early */
|
|
thread_state = THREAD_STARTING;
|
|
task = g_task_new (NULL, cancellable, run_in_thread_sync_callback, NULL);
|
|
g_task_set_return_on_cancel (task, TRUE);
|
|
|
|
g_task_set_task_data (task, (gpointer)&thread_state, NULL);
|
|
g_mutex_lock (&roc_init_mutex);
|
|
g_mutex_lock (&roc_finish_mutex);
|
|
runner_thread = g_thread_new ("return-on-cancel-sync runner thread",
|
|
cancel_sync_runner_thread, task);
|
|
|
|
while (thread_state == THREAD_STARTING)
|
|
g_cond_wait (&roc_init_cond, &roc_init_mutex);
|
|
g_mutex_unlock (&roc_init_mutex);
|
|
|
|
g_assert (thread_state == THREAD_RUNNING);
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
g_thread_join (runner_thread);
|
|
g_assert (thread_state == THREAD_RUNNING);
|
|
|
|
ret = g_task_propagate_int (task, &error);
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_clear_error (&error);
|
|
g_assert_cmpint (ret, ==, -1);
|
|
|
|
g_object_unref (task);
|
|
|
|
while (thread_state == THREAD_RUNNING)
|
|
g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
|
|
g_mutex_unlock (&roc_finish_mutex);
|
|
|
|
g_assert (thread_state == THREAD_CANCELLED);
|
|
|
|
g_cancellable_reset (cancellable);
|
|
|
|
/* If the task is already cancelled before it starts, it returns
|
|
* immediately, but the thread func still runs.
|
|
*/
|
|
thread_state = THREAD_STARTING;
|
|
task = g_task_new (NULL, cancellable, run_in_thread_sync_callback, NULL);
|
|
g_task_set_return_on_cancel (task, TRUE);
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
|
|
g_task_set_task_data (task, (gpointer)&thread_state, NULL);
|
|
g_mutex_lock (&roc_init_mutex);
|
|
g_mutex_lock (&roc_finish_mutex);
|
|
runner_thread = g_thread_new ("return-on-cancel-sync runner thread",
|
|
cancel_sync_runner_thread, task);
|
|
|
|
g_thread_join (runner_thread);
|
|
g_assert (thread_state == THREAD_STARTING);
|
|
|
|
ret = g_task_propagate_int (task, &error);
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_clear_error (&error);
|
|
g_assert_cmpint (ret, ==, -1);
|
|
|
|
g_object_unref (task);
|
|
|
|
while (thread_state == THREAD_STARTING)
|
|
g_cond_wait (&roc_init_cond, &roc_init_mutex);
|
|
g_mutex_unlock (&roc_init_mutex);
|
|
|
|
g_assert (thread_state == THREAD_RUNNING);
|
|
|
|
while (thread_state == THREAD_RUNNING)
|
|
g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
|
|
g_mutex_unlock (&roc_finish_mutex);
|
|
|
|
g_assert (thread_state == THREAD_CANCELLED);
|
|
|
|
g_object_unref (cancellable);
|
|
}
|
|
|
|
/* test_return_on_cancel_atomic: turning return-on-cancel on/off is
|
|
* non-racy
|
|
*/
|
|
|
|
GMutex roca_mutex_1, roca_mutex_2;
|
|
GCond roca_cond_1, roca_cond_2;
|
|
|
|
static void
|
|
return_on_cancel_atomic_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gboolean *callback_ran = user_data;
|
|
GError *error = NULL;
|
|
gssize ret;
|
|
|
|
g_assert (g_thread_self () == main_thread);
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (g_task_had_error (G_TASK (result)));
|
|
|
|
ret = g_task_propagate_int (G_TASK (result), &error);
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_clear_error (&error);
|
|
g_assert_cmpint (ret, ==, -1);
|
|
|
|
*callback_ran = TRUE;
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static gboolean
|
|
idle_quit_loop (gpointer user_data)
|
|
{
|
|
g_main_loop_quit (loop);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
return_on_cancel_atomic_thread (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
gint *state = task_data;
|
|
|
|
g_assert (source_object == g_task_get_source_object (task));
|
|
g_assert (task_data == g_task_get_task_data (task));
|
|
g_assert (cancellable == g_task_get_cancellable (task));
|
|
|
|
g_assert (g_thread_self () != main_thread);
|
|
g_assert_cmpint (*state, ==, 0);
|
|
|
|
g_mutex_lock (&roca_mutex_1);
|
|
*state = 1;
|
|
g_cond_signal (&roca_cond_1);
|
|
g_mutex_unlock (&roca_mutex_1);
|
|
|
|
g_mutex_lock (&roca_mutex_2);
|
|
if (g_task_set_return_on_cancel (task, FALSE))
|
|
*state = 2;
|
|
else
|
|
*state = 3;
|
|
g_cond_signal (&roca_cond_2);
|
|
g_mutex_unlock (&roca_mutex_2);
|
|
|
|
g_mutex_lock (&roca_mutex_1);
|
|
if (g_task_set_return_on_cancel (task, TRUE))
|
|
*state = 4;
|
|
else
|
|
*state = 5;
|
|
g_cond_signal (&roca_cond_1);
|
|
g_mutex_unlock (&roca_mutex_1);
|
|
|
|
g_mutex_lock (&roca_mutex_2);
|
|
if (g_task_set_return_on_cancel (task, TRUE))
|
|
*state = 6;
|
|
else
|
|
*state = 7;
|
|
g_cond_signal (&roca_cond_2);
|
|
g_mutex_unlock (&roca_mutex_2);
|
|
|
|
g_task_return_int (task, magic);
|
|
}
|
|
|
|
static void
|
|
test_return_on_cancel_atomic (void)
|
|
{
|
|
GTask *task;
|
|
GCancellable *cancellable;
|
|
volatile gint state;
|
|
gboolean callback_ran;
|
|
|
|
cancellable = g_cancellable_new ();
|
|
g_mutex_lock (&roca_mutex_1);
|
|
g_mutex_lock (&roca_mutex_2);
|
|
|
|
/* If we don't cancel it, each set_return_on_cancel() call will succeed */
|
|
state = 0;
|
|
callback_ran = FALSE;
|
|
task = g_task_new (NULL, cancellable, return_on_cancel_atomic_callback, &callback_ran);
|
|
g_task_set_return_on_cancel (task, TRUE);
|
|
|
|
g_task_set_task_data (task, (gpointer)&state, NULL);
|
|
g_task_run_in_thread (task, return_on_cancel_atomic_thread);
|
|
g_object_unref (task);
|
|
|
|
g_assert_cmpint (state, ==, 0);
|
|
|
|
while (state == 0)
|
|
g_cond_wait (&roca_cond_1, &roca_mutex_1);
|
|
g_assert (state == 1);
|
|
|
|
while (state == 1)
|
|
g_cond_wait (&roca_cond_2, &roca_mutex_2);
|
|
g_assert (state == 2);
|
|
|
|
while (state == 2)
|
|
g_cond_wait (&roca_cond_1, &roca_mutex_1);
|
|
g_assert (state == 4);
|
|
|
|
while (state == 4)
|
|
g_cond_wait (&roca_cond_2, &roca_mutex_2);
|
|
g_assert (state == 6);
|
|
|
|
/* callback assumes there'll be a cancelled error */
|
|
g_cancellable_cancel (cancellable);
|
|
|
|
g_assert (callback_ran == FALSE);
|
|
g_main_loop_run (loop);
|
|
g_assert (callback_ran == TRUE);
|
|
|
|
g_cancellable_reset (cancellable);
|
|
|
|
|
|
/* If we cancel while it's temporarily not return-on-cancel, the
|
|
* task won't complete right away, and further
|
|
* g_task_set_return_on_cancel() calls will return FALSE.
|
|
*/
|
|
state = 0;
|
|
callback_ran = FALSE;
|
|
task = g_task_new (NULL, cancellable, return_on_cancel_atomic_callback, &callback_ran);
|
|
g_task_set_return_on_cancel (task, TRUE);
|
|
|
|
g_task_set_task_data (task, (gpointer)&state, NULL);
|
|
g_task_run_in_thread (task, return_on_cancel_atomic_thread);
|
|
g_object_unref (task);
|
|
|
|
g_assert_cmpint (state, ==, 0);
|
|
|
|
while (state == 0)
|
|
g_cond_wait (&roca_cond_1, &roca_mutex_1);
|
|
g_assert (state == 1);
|
|
g_assert (g_task_get_return_on_cancel (task));
|
|
|
|
while (state == 1)
|
|
g_cond_wait (&roca_cond_2, &roca_mutex_2);
|
|
g_assert (state == 2);
|
|
g_assert (!g_task_get_return_on_cancel (task));
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
g_idle_add (idle_quit_loop, NULL);
|
|
g_main_loop_run (loop);
|
|
g_assert (callback_ran == FALSE);
|
|
|
|
while (state == 2)
|
|
g_cond_wait (&roca_cond_1, &roca_mutex_1);
|
|
g_assert (state == 5);
|
|
g_assert (!g_task_get_return_on_cancel (task));
|
|
|
|
g_main_loop_run (loop);
|
|
g_assert (callback_ran == TRUE);
|
|
|
|
while (state == 5)
|
|
g_cond_wait (&roca_cond_2, &roca_mutex_2);
|
|
g_assert (state == 7);
|
|
|
|
g_object_unref (cancellable);
|
|
g_mutex_unlock (&roca_mutex_1);
|
|
g_mutex_unlock (&roca_mutex_2);
|
|
}
|
|
|
|
/* test_return_pointer: memory management of pointer returns */
|
|
|
|
static void
|
|
test_return_pointer (void)
|
|
{
|
|
GObject *object, *ret;
|
|
GTask *task;
|
|
GCancellable *cancellable;
|
|
GError *error = NULL;
|
|
|
|
/* If we don't read back the return value, the task will
|
|
* run its destroy notify.
|
|
*/
|
|
object = (GObject *)g_dummy_object_new ();
|
|
g_assert_cmpint (object->ref_count, ==, 1);
|
|
g_object_add_weak_pointer (object, (gpointer *)&object);
|
|
|
|
task = g_task_new (NULL, NULL, NULL, NULL);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
g_task_return_pointer (task, object, g_object_unref);
|
|
g_assert_cmpint (object->ref_count, ==, 1);
|
|
|
|
g_object_unref (task);
|
|
g_assert (task == NULL);
|
|
g_assert (object == NULL);
|
|
|
|
/* Likewise, if the return value is overwritten by an error */
|
|
object = (GObject *)g_dummy_object_new ();
|
|
g_assert_cmpint (object->ref_count, ==, 1);
|
|
g_object_add_weak_pointer (object, (gpointer *)&object);
|
|
|
|
cancellable = g_cancellable_new ();
|
|
task = g_task_new (NULL, cancellable, NULL, NULL);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
g_task_return_pointer (task, object, g_object_unref);
|
|
g_assert_cmpint (object->ref_count, ==, 1);
|
|
g_cancellable_cancel (cancellable);
|
|
g_assert_cmpint (object->ref_count, ==, 1);
|
|
|
|
ret = g_task_propagate_pointer (task, &error);
|
|
g_assert (ret == NULL);
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_clear_error (&error);
|
|
g_assert_cmpint (object->ref_count, ==, 1);
|
|
|
|
g_object_unref (task);
|
|
g_object_unref (cancellable);
|
|
g_assert (task == NULL);
|
|
g_assert (object == NULL);
|
|
|
|
/* If we read back the return value, we steal its ref */
|
|
object = (GObject *)g_dummy_object_new ();
|
|
g_assert_cmpint (object->ref_count, ==, 1);
|
|
g_object_add_weak_pointer (object, (gpointer *)&object);
|
|
|
|
task = g_task_new (NULL, NULL, NULL, NULL);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
g_task_return_pointer (task, object, g_object_unref);
|
|
g_assert_cmpint (object->ref_count, ==, 1);
|
|
|
|
ret = g_task_propagate_pointer (task, &error);
|
|
g_assert_no_error (error);
|
|
g_assert (ret == object);
|
|
g_assert_cmpint (object->ref_count, ==, 1);
|
|
|
|
g_object_unref (task);
|
|
g_assert (task == NULL);
|
|
g_assert_cmpint (object->ref_count, ==, 1);
|
|
g_object_unref (object);
|
|
g_assert (object == NULL);
|
|
}
|
|
|
|
/* test_object_keepalive: GTask takes a ref on its source object */
|
|
|
|
static GObject *keepalive_object;
|
|
|
|
static void
|
|
keepalive_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gssize *result_out = user_data;
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == keepalive_object);
|
|
g_assert (g_task_is_valid (result, object));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
g_assert (!g_task_had_error (G_TASK (result)));
|
|
|
|
*result_out = g_task_propagate_int (G_TASK (result), &error);
|
|
g_assert_no_error (error);
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
test_object_keepalive (void)
|
|
{
|
|
GObject *object;
|
|
GTask *task;
|
|
gssize result;
|
|
int ref_count;
|
|
|
|
keepalive_object = object = (GObject *)g_dummy_object_new ();
|
|
g_object_add_weak_pointer (object, (gpointer *)&object);
|
|
|
|
task = g_task_new (object, NULL, keepalive_callback, &result);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
|
|
ref_count = object->ref_count;
|
|
g_assert_cmpint (ref_count, >, 1);
|
|
|
|
g_assert (g_task_get_source_object (task) == object);
|
|
g_assert (g_async_result_get_source_object (G_ASYNC_RESULT (task)) == object);
|
|
g_assert_cmpint (object->ref_count, ==, ref_count + 1);
|
|
g_object_unref (object);
|
|
|
|
g_object_unref (object);
|
|
g_assert (object != NULL);
|
|
|
|
g_task_return_int (task, magic);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert (object != NULL);
|
|
g_assert_cmpint (result, ==, magic);
|
|
|
|
g_object_unref (task);
|
|
g_assert (task == NULL);
|
|
g_assert (object == NULL);
|
|
}
|
|
|
|
/* test_legacy_error: legacy GSimpleAsyncResult handling */
|
|
static void test_legacy_error (void);
|
|
|
|
static void
|
|
legacy_error_callback (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
gssize *result_out = user_data;
|
|
GError *error = NULL;
|
|
|
|
g_assert (object == NULL);
|
|
g_assert (g_async_result_is_tagged (result, test_legacy_error));
|
|
g_assert (g_async_result_get_user_data (result) == user_data);
|
|
|
|
if (g_async_result_legacy_propagate_error (result, &error))
|
|
{
|
|
g_assert (!g_task_is_valid (result, object));
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
|
g_assert (g_simple_async_result_is_valid (result, object, test_legacy_error));
|
|
G_GNUC_END_IGNORE_DEPRECATIONS;
|
|
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
|
|
*result_out = -2;
|
|
}
|
|
else
|
|
{
|
|
g_assert (g_task_is_valid (result, object));
|
|
|
|
*result_out = g_task_propagate_int (G_TASK (result), NULL);
|
|
/* Might be error, might not */
|
|
}
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static gboolean
|
|
legacy_error_return (gpointer user_data)
|
|
{
|
|
if (G_IS_TASK (user_data))
|
|
{
|
|
GTask *task = user_data;
|
|
|
|
g_task_return_int (task, magic);
|
|
g_object_unref (task);
|
|
}
|
|
else
|
|
{
|
|
GSimpleAsyncResult *simple = user_data;
|
|
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
|
g_simple_async_result_set_error (simple,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Failed");
|
|
g_simple_async_result_complete (simple);
|
|
G_GNUC_END_IGNORE_DEPRECATIONS;
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
test_legacy_error (void)
|
|
{
|
|
GTask *task;
|
|
GSimpleAsyncResult *simple;
|
|
gssize result;
|
|
|
|
/* GTask success */
|
|
task = g_task_new (NULL, NULL, legacy_error_callback, &result);
|
|
g_task_set_source_tag (task, test_legacy_error);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
|
|
g_idle_add (legacy_error_return, task);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (result, ==, magic);
|
|
g_assert (task == NULL);
|
|
|
|
/* GTask error */
|
|
task = g_task_new (NULL, NULL, legacy_error_callback, &result);
|
|
g_task_set_source_tag (task, test_legacy_error);
|
|
g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
|
|
|
|
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed");
|
|
g_object_unref (task);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (result, ==, -1);
|
|
g_assert (task == NULL);
|
|
|
|
/* GSimpleAsyncResult error */
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
|
simple = g_simple_async_result_new (NULL, legacy_error_callback, &result,
|
|
test_legacy_error);
|
|
G_GNUC_END_IGNORE_DEPRECATIONS;
|
|
g_object_add_weak_pointer (G_OBJECT (simple), (gpointer *)&simple);
|
|
|
|
g_idle_add (legacy_error_return, simple);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (result, ==, -2);
|
|
g_assert (simple == NULL);
|
|
}
|
|
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
int ret;
|
|
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
main_thread = g_thread_self ();
|
|
magic = g_get_monotonic_time ();
|
|
|
|
g_test_add_func ("/gtask/basic", test_basic);
|
|
g_test_add_func ("/gtask/error", test_error);
|
|
g_test_add_func ("/gtask/return-from-same-iteration", test_return_from_same_iteration);
|
|
g_test_add_func ("/gtask/return-from-toplevel", test_return_from_toplevel);
|
|
g_test_add_func ("/gtask/return-from-anon-thread", test_return_from_anon_thread);
|
|
g_test_add_func ("/gtask/return-from-wrong-thread", test_return_from_wrong_thread);
|
|
g_test_add_func ("/gtask/no-callback", test_no_callback);
|
|
g_test_add_func ("/gtask/report-error", test_report_error);
|
|
g_test_add_func ("/gtask/priority", test_priority);
|
|
g_test_add_func ("/gtask/check-cancellable", test_check_cancellable);
|
|
g_test_add_func ("/gtask/return-if-cancelled", test_return_if_cancelled);
|
|
g_test_add_func ("/gtask/run-in-thread", test_run_in_thread);
|
|
g_test_add_func ("/gtask/run-in-thread-sync", test_run_in_thread_sync);
|
|
g_test_add_func ("/gtask/run-in-thread-priority", test_run_in_thread_priority);
|
|
g_test_add_func ("/gtask/run-in-thread-kind", test_run_in_thread_kind);
|
|
g_test_add_func ("/gtask/return-on-cancel", test_return_on_cancel);
|
|
g_test_add_func ("/gtask/return-on-cancel-sync", test_return_on_cancel_sync);
|
|
g_test_add_func ("/gtask/return-on-cancel-atomic", test_return_on_cancel_atomic);
|
|
g_test_add_func ("/gtask/return-pointer", test_return_pointer);
|
|
g_test_add_func ("/gtask/object-keepalive", test_object_keepalive);
|
|
g_test_add_func ("/gtask/legacy-error", test_legacy_error);
|
|
|
|
ret = g_test_run();
|
|
|
|
g_main_loop_unref (loop);
|
|
|
|
return ret;
|
|
}
|