2011-08-12 11:49:31 +02:00
|
|
|
|
/* GIO - GLib Input, Output and Streaming Library
|
|
|
|
|
*
|
|
|
|
|
* Copyright (C) 2011 Collabora Ltd.
|
|
|
|
|
*
|
2022-05-18 10:20:07 +02:00
|
|
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
|
*
|
2011-08-12 11:49:31 +02:00
|
|
|
|
* 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
|
2017-05-27 17:19:21 +02:00
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2011-08-12 11:49:31 +02:00
|
|
|
|
*
|
|
|
|
|
* 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
|
2014-01-23 12:58:29 +01:00
|
|
|
|
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
2011-08-12 11:49:31 +02:00
|
|
|
|
*
|
|
|
|
|
* Author: Stef Walter <stefw@collabora.co.uk>
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <locale.h>
|
|
|
|
|
|
|
|
|
|
#include <gio/gio.h>
|
|
|
|
|
|
2021-01-29 20:28:25 +01:00
|
|
|
|
#include "glib/glib-private.h"
|
|
|
|
|
|
2011-08-12 11:49:31 +02:00
|
|
|
|
/* How long to wait in ms for each iteration */
|
|
|
|
|
#define WAIT_ITERATION (10)
|
|
|
|
|
|
|
|
|
|
static gint num_async_operations = 0;
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
2020-06-17 19:30:56 +02:00
|
|
|
|
guint iterations_requested; /* construct-only */
|
|
|
|
|
guint iterations_done; /* (atomic) */
|
2011-08-12 11:49:31 +02:00
|
|
|
|
} MockOperationData;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
mock_operation_free (gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
MockOperationData *data = user_data;
|
|
|
|
|
g_free (data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2012-08-02 21:45:24 +02:00
|
|
|
|
mock_operation_thread (GTask *task,
|
|
|
|
|
gpointer source_object,
|
|
|
|
|
gpointer task_data,
|
|
|
|
|
GCancellable *cancellable)
|
2011-08-12 11:49:31 +02:00
|
|
|
|
{
|
2012-08-02 21:45:24 +02:00
|
|
|
|
MockOperationData *data = task_data;
|
2011-08-12 11:49:31 +02:00
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < data->iterations_requested; i++)
|
|
|
|
|
{
|
2012-08-02 21:45:24 +02:00
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
2011-08-12 11:49:31 +02:00
|
|
|
|
break;
|
|
|
|
|
if (g_test_verbose ())
|
2020-06-17 19:16:45 +02:00
|
|
|
|
g_test_message ("THRD: %u iteration %u", data->iterations_requested, i);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
g_usleep (WAIT_ITERATION * 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_test_verbose ())
|
2020-06-17 19:16:45 +02:00
|
|
|
|
g_test_message ("THRD: %u stopped at %u", data->iterations_requested, i);
|
2020-06-17 19:30:56 +02:00
|
|
|
|
g_atomic_int_add (&data->iterations_done, i);
|
2012-08-02 21:45:24 +02:00
|
|
|
|
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
mock_operation_timeout (gpointer user_data)
|
|
|
|
|
{
|
2012-08-02 21:45:24 +02:00
|
|
|
|
GTask *task;
|
2011-08-12 11:49:31 +02:00
|
|
|
|
MockOperationData *data;
|
|
|
|
|
gboolean done = FALSE;
|
2020-06-17 19:30:56 +02:00
|
|
|
|
guint iterations_done;
|
2011-08-12 11:49:31 +02:00
|
|
|
|
|
2012-08-02 21:45:24 +02:00
|
|
|
|
task = G_TASK (user_data);
|
|
|
|
|
data = g_task_get_task_data (task);
|
2020-06-17 19:30:56 +02:00
|
|
|
|
iterations_done = g_atomic_int_get (&data->iterations_done);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
|
2020-06-17 19:30:56 +02:00
|
|
|
|
if (iterations_done >= data->iterations_requested)
|
2011-08-12 11:49:31 +02:00
|
|
|
|
done = TRUE;
|
|
|
|
|
|
2012-08-02 21:45:24 +02:00
|
|
|
|
if (g_cancellable_is_cancelled (g_task_get_cancellable (task)))
|
2011-08-12 11:49:31 +02:00
|
|
|
|
done = TRUE;
|
|
|
|
|
|
2012-08-02 21:45:24 +02:00
|
|
|
|
if (done)
|
|
|
|
|
{
|
2011-08-12 11:49:31 +02:00
|
|
|
|
if (g_test_verbose ())
|
2020-06-17 19:16:45 +02:00
|
|
|
|
g_test_message ("LOOP: %u stopped at %u",
|
2020-06-17 19:30:56 +02:00
|
|
|
|
data->iterations_requested, iterations_done);
|
2012-08-02 21:45:24 +02:00
|
|
|
|
g_task_return_boolean (task, TRUE);
|
2020-06-17 19:31:45 +02:00
|
|
|
|
return G_SOURCE_REMOVE;
|
2012-08-02 21:45:24 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-06-17 19:30:56 +02:00
|
|
|
|
g_atomic_int_inc (&data->iterations_done);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
if (g_test_verbose ())
|
2020-06-17 19:16:45 +02:00
|
|
|
|
g_test_message ("LOOP: %u iteration %u",
|
2020-06-17 19:30:56 +02:00
|
|
|
|
data->iterations_requested, iterations_done + 1);
|
2020-06-17 19:31:45 +02:00
|
|
|
|
return G_SOURCE_CONTINUE;
|
2011-08-12 11:49:31 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
mock_operation_async (guint wait_iterations,
|
|
|
|
|
gboolean run_in_thread,
|
|
|
|
|
GCancellable *cancellable,
|
|
|
|
|
GAsyncReadyCallback callback,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
2012-08-02 21:45:24 +02:00
|
|
|
|
GTask *task;
|
2011-08-12 11:49:31 +02:00
|
|
|
|
MockOperationData *data;
|
|
|
|
|
|
2012-08-02 21:45:24 +02:00
|
|
|
|
task = g_task_new (NULL, cancellable, callback, user_data);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
data = g_new0 (MockOperationData, 1);
|
|
|
|
|
data->iterations_requested = wait_iterations;
|
2012-08-02 21:45:24 +02:00
|
|
|
|
g_task_set_task_data (task, data, mock_operation_free);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
|
2012-08-02 21:45:24 +02:00
|
|
|
|
if (run_in_thread)
|
|
|
|
|
{
|
|
|
|
|
g_task_run_in_thread (task, mock_operation_thread);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
if (g_test_verbose ())
|
2020-06-17 19:16:45 +02:00
|
|
|
|
g_test_message ("THRD: %d started", wait_iterations);
|
2012-08-02 21:45:24 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2011-08-12 11:49:31 +02:00
|
|
|
|
g_timeout_add_full (G_PRIORITY_DEFAULT, WAIT_ITERATION, mock_operation_timeout,
|
2012-08-02 21:45:24 +02:00
|
|
|
|
g_object_ref (task), g_object_unref);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
if (g_test_verbose ())
|
2020-06-17 19:16:45 +02:00
|
|
|
|
g_test_message ("LOOP: %d started", wait_iterations);
|
2012-08-02 21:45:24 +02:00
|
|
|
|
}
|
2011-08-12 11:49:31 +02:00
|
|
|
|
|
2012-08-02 21:45:24 +02:00
|
|
|
|
g_object_unref (task);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static guint
|
|
|
|
|
mock_operation_finish (GAsyncResult *result,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
MockOperationData *data;
|
2012-08-02 21:45:24 +02:00
|
|
|
|
GTask *task;
|
|
|
|
|
|
2020-02-28 16:15:02 +01:00
|
|
|
|
g_assert_true (g_task_is_valid (result, NULL));
|
2011-08-12 11:49:31 +02:00
|
|
|
|
|
2012-08-02 21:45:24 +02:00
|
|
|
|
/* This test expects the return value to be iterations_done even
|
|
|
|
|
* when an error is set.
|
|
|
|
|
*/
|
|
|
|
|
task = G_TASK (result);
|
|
|
|
|
data = g_task_get_task_data (task);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
|
2012-08-02 21:45:24 +02:00
|
|
|
|
g_task_propagate_boolean (task, error);
|
2020-06-17 19:30:56 +02:00
|
|
|
|
return g_atomic_int_get (&data->iterations_done);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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--;
|
2020-06-17 19:32:28 +02:00
|
|
|
|
g_main_context_wakeup (NULL);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_cancel_multiple_concurrent (void)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable;
|
|
|
|
|
guint i, iterations;
|
|
|
|
|
|
2019-11-21 10:59:03 +01:00
|
|
|
|
if (!g_test_thorough ())
|
|
|
|
|
{
|
|
|
|
|
g_test_skip ("Not running timing heavy test");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-12 11:49:31 +02:00
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
|
|
|
|
|
|
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++;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-17 19:32:28 +02:00
|
|
|
|
/* Wait for the threads to start up */
|
|
|
|
|
while (num_async_operations != 45)
|
|
|
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
|
g_assert_cmpint (num_async_operations, ==, 45);\
|
|
|
|
|
|
2011-08-12 11:49:31 +02:00
|
|
|
|
if (g_test_verbose ())
|
2020-06-17 19:16:45 +02:00
|
|
|
|
g_test_message ("CANCEL: %d operations", num_async_operations);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
g_cancellable_cancel (cancellable);
|
2020-02-28 16:15:02 +01:00
|
|
|
|
g_assert_true (g_cancellable_is_cancelled (cancellable));
|
2011-08-12 11:49:31 +02:00
|
|
|
|
|
2012-08-24 23:11:38 +02:00
|
|
|
|
/* Wait for all operations to be cancelled */
|
2020-06-17 19:32:28 +02:00
|
|
|
|
while (num_async_operations != 0)
|
|
|
|
|
g_main_context_iteration (NULL, TRUE);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
g_assert_cmpint (num_async_operations, ==, 0);
|
|
|
|
|
|
|
|
|
|
g_object_unref (cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-05 16:44:16 +01:00
|
|
|
|
static void
|
|
|
|
|
test_cancel_null (void)
|
|
|
|
|
{
|
|
|
|
|
g_cancellable_cancel (NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 15:44:44 +01:00
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
GCond cond;
|
|
|
|
|
GMutex mutex;
|
tests: Speed up the cancellable test
The test added for #1841 spawned 100000 threads. That was fine on a
desktop machine, but on a heavily loaded CI machine, it could result in
large (and unpredictable) slowdowns, resulting in the test taking over
120s in about 1 in 5 runs, and hence failing that CI pipeline due to a
timeout. When passing normally on CI, the test would take around 90s.
Here’s a histogram of time per iteration on a failing (timed out) test
run. Each iteration is one thread spawn:
Iteration duration (µs) | Frequency
------------------------+----------
≤100 | 0
100–200 | 30257
200–400 | 13696
400–800 | 1046
800–1000 | 123
1000–2000 | 583
2000–4000 | 3779
4000–8000 | 4972
8000–10000 | 1027
10000–20000 | 2610
20000–40000 | 650
40000–80000 | 86
80000–100000 | 10
100000–200000 | 2
>200000 | 0
There’s no actual need for the test to spawn 100000 threads, so rewrite
it to reuse a single thread, and pass new data to that thread.
Reverting the original commit (e4a690f5dd95) reproduces the failure on
100 out of 100 test runs with this commit applied, so the test still
works.
The test now takes 3s, rather than 11s, to run on my computer, and has
passed when run with `meson test --repeat 1000 cancellable`.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-05-20 17:51:03 +02:00
|
|
|
|
gboolean thread_ready;
|
|
|
|
|
GAsyncQueue *cancellable_source_queue; /* (owned) (element-type GCancellableSource) */
|
2020-02-21 15:44:44 +01:00
|
|
|
|
} ThreadedDisposeData;
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
cancelled_cb (GCancellable *cancellable,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
/* Nothing needs to be done here. */
|
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gpointer
|
|
|
|
|
threaded_dispose_thread_cb (gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
ThreadedDisposeData *data = user_data;
|
tests: Speed up the cancellable test
The test added for #1841 spawned 100000 threads. That was fine on a
desktop machine, but on a heavily loaded CI machine, it could result in
large (and unpredictable) slowdowns, resulting in the test taking over
120s in about 1 in 5 runs, and hence failing that CI pipeline due to a
timeout. When passing normally on CI, the test would take around 90s.
Here’s a histogram of time per iteration on a failing (timed out) test
run. Each iteration is one thread spawn:
Iteration duration (µs) | Frequency
------------------------+----------
≤100 | 0
100–200 | 30257
200–400 | 13696
400–800 | 1046
800–1000 | 123
1000–2000 | 583
2000–4000 | 3779
4000–8000 | 4972
8000–10000 | 1027
10000–20000 | 2610
20000–40000 | 650
40000–80000 | 86
80000–100000 | 10
100000–200000 | 2
>200000 | 0
There’s no actual need for the test to spawn 100000 threads, so rewrite
it to reuse a single thread, and pass new data to that thread.
Reverting the original commit (e4a690f5dd95) reproduces the failure on
100 out of 100 test runs with this commit applied, so the test still
works.
The test now takes 3s, rather than 11s, to run on my computer, and has
passed when run with `meson test --repeat 1000 cancellable`.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-05-20 17:51:03 +02:00
|
|
|
|
GSource *cancellable_source;
|
2020-02-21 15:44:44 +01:00
|
|
|
|
|
|
|
|
|
g_mutex_lock (&data->mutex);
|
tests: Speed up the cancellable test
The test added for #1841 spawned 100000 threads. That was fine on a
desktop machine, but on a heavily loaded CI machine, it could result in
large (and unpredictable) slowdowns, resulting in the test taking over
120s in about 1 in 5 runs, and hence failing that CI pipeline due to a
timeout. When passing normally on CI, the test would take around 90s.
Here’s a histogram of time per iteration on a failing (timed out) test
run. Each iteration is one thread spawn:
Iteration duration (µs) | Frequency
------------------------+----------
≤100 | 0
100–200 | 30257
200–400 | 13696
400–800 | 1046
800–1000 | 123
1000–2000 | 583
2000–4000 | 3779
4000–8000 | 4972
8000–10000 | 1027
10000–20000 | 2610
20000–40000 | 650
40000–80000 | 86
80000–100000 | 10
100000–200000 | 2
>200000 | 0
There’s no actual need for the test to spawn 100000 threads, so rewrite
it to reuse a single thread, and pass new data to that thread.
Reverting the original commit (e4a690f5dd95) reproduces the failure on
100 out of 100 test runs with this commit applied, so the test still
works.
The test now takes 3s, rather than 11s, to run on my computer, and has
passed when run with `meson test --repeat 1000 cancellable`.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-05-20 17:51:03 +02:00
|
|
|
|
data->thread_ready = TRUE;
|
2020-02-21 15:44:44 +01:00
|
|
|
|
g_cond_broadcast (&data->cond);
|
|
|
|
|
g_mutex_unlock (&data->mutex);
|
|
|
|
|
|
tests: Speed up the cancellable test
The test added for #1841 spawned 100000 threads. That was fine on a
desktop machine, but on a heavily loaded CI machine, it could result in
large (and unpredictable) slowdowns, resulting in the test taking over
120s in about 1 in 5 runs, and hence failing that CI pipeline due to a
timeout. When passing normally on CI, the test would take around 90s.
Here’s a histogram of time per iteration on a failing (timed out) test
run. Each iteration is one thread spawn:
Iteration duration (µs) | Frequency
------------------------+----------
≤100 | 0
100–200 | 30257
200–400 | 13696
400–800 | 1046
800–1000 | 123
1000–2000 | 583
2000–4000 | 3779
4000–8000 | 4972
8000–10000 | 1027
10000–20000 | 2610
20000–40000 | 650
40000–80000 | 86
80000–100000 | 10
100000–200000 | 2
>200000 | 0
There’s no actual need for the test to spawn 100000 threads, so rewrite
it to reuse a single thread, and pass new data to that thread.
Reverting the original commit (e4a690f5dd95) reproduces the failure on
100 out of 100 test runs with this commit applied, so the test still
works.
The test now takes 3s, rather than 11s, to run on my computer, and has
passed when run with `meson test --repeat 1000 cancellable`.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-05-20 17:51:03 +02:00
|
|
|
|
while ((cancellable_source = g_async_queue_pop (data->cancellable_source_queue)) != (gpointer) 1)
|
|
|
|
|
{
|
|
|
|
|
/* Race with cancellation of the cancellable. */
|
|
|
|
|
g_source_unref (cancellable_source);
|
|
|
|
|
}
|
2020-02-21 15:44:44 +01:00
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_cancellable_source_threaded_dispose (void)
|
|
|
|
|
{
|
tests: Speed up the cancellable test
The test added for #1841 spawned 100000 threads. That was fine on a
desktop machine, but on a heavily loaded CI machine, it could result in
large (and unpredictable) slowdowns, resulting in the test taking over
120s in about 1 in 5 runs, and hence failing that CI pipeline due to a
timeout. When passing normally on CI, the test would take around 90s.
Here’s a histogram of time per iteration on a failing (timed out) test
run. Each iteration is one thread spawn:
Iteration duration (µs) | Frequency
------------------------+----------
≤100 | 0
100–200 | 30257
200–400 | 13696
400–800 | 1046
800–1000 | 123
1000–2000 | 583
2000–4000 | 3779
4000–8000 | 4972
8000–10000 | 1027
10000–20000 | 2610
20000–40000 | 650
40000–80000 | 86
80000–100000 | 10
100000–200000 | 2
>200000 | 0
There’s no actual need for the test to spawn 100000 threads, so rewrite
it to reuse a single thread, and pass new data to that thread.
Reverting the original commit (e4a690f5dd95) reproduces the failure on
100 out of 100 test runs with this commit applied, so the test still
works.
The test now takes 3s, rather than 11s, to run on my computer, and has
passed when run with `meson test --repeat 1000 cancellable`.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-05-20 17:51:03 +02:00
|
|
|
|
ThreadedDisposeData data;
|
|
|
|
|
GThread *thread = NULL;
|
2020-02-21 15:44:44 +01:00
|
|
|
|
guint i;
|
2020-06-17 19:06:20 +02:00
|
|
|
|
GPtrArray *cancellables_pending_unref = g_ptr_array_new_with_free_func (g_object_unref);
|
2020-02-21 15:44:44 +01:00
|
|
|
|
|
|
|
|
|
g_test_summary ("Test a thread race between disposing of a GCancellableSource "
|
|
|
|
|
"(in one thread) and cancelling the GCancellable it refers "
|
|
|
|
|
"to (in another thread)");
|
|
|
|
|
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/1841");
|
2022-06-21 05:58:00 +02:00
|
|
|
|
#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
|
2020-02-21 15:44:44 +01:00
|
|
|
|
|
tests: Speed up the cancellable test
The test added for #1841 spawned 100000 threads. That was fine on a
desktop machine, but on a heavily loaded CI machine, it could result in
large (and unpredictable) slowdowns, resulting in the test taking over
120s in about 1 in 5 runs, and hence failing that CI pipeline due to a
timeout. When passing normally on CI, the test would take around 90s.
Here’s a histogram of time per iteration on a failing (timed out) test
run. Each iteration is one thread spawn:
Iteration duration (µs) | Frequency
------------------------+----------
≤100 | 0
100–200 | 30257
200–400 | 13696
400–800 | 1046
800–1000 | 123
1000–2000 | 583
2000–4000 | 3779
4000–8000 | 4972
8000–10000 | 1027
10000–20000 | 2610
20000–40000 | 650
40000–80000 | 86
80000–100000 | 10
100000–200000 | 2
>200000 | 0
There’s no actual need for the test to spawn 100000 threads, so rewrite
it to reuse a single thread, and pass new data to that thread.
Reverting the original commit (e4a690f5dd95) reproduces the failure on
100 out of 100 test runs with this commit applied, so the test still
works.
The test now takes 3s, rather than 11s, to run on my computer, and has
passed when run with `meson test --repeat 1000 cancellable`.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-05-20 17:51:03 +02:00
|
|
|
|
/* Create a new thread and wait until it’s ready to execute. Each iteration of
|
|
|
|
|
* the test will pass it a new #GCancellableSource. */
|
|
|
|
|
g_cond_init (&data.cond);
|
|
|
|
|
g_mutex_init (&data.mutex);
|
|
|
|
|
data.cancellable_source_queue = g_async_queue_new_full ((GDestroyNotify) g_source_unref);
|
|
|
|
|
data.thread_ready = FALSE;
|
|
|
|
|
|
|
|
|
|
g_mutex_lock (&data.mutex);
|
|
|
|
|
thread = g_thread_new ("/cancellable-source/threaded-dispose",
|
|
|
|
|
threaded_dispose_thread_cb, &data);
|
|
|
|
|
|
|
|
|
|
while (!data.thread_ready)
|
|
|
|
|
g_cond_wait (&data.cond, &data.mutex);
|
|
|
|
|
g_mutex_unlock (&data.mutex);
|
|
|
|
|
|
2020-02-21 15:44:44 +01:00
|
|
|
|
for (i = 0; i < 100000; i++)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable = NULL;
|
|
|
|
|
GSource *cancellable_source = NULL;
|
|
|
|
|
|
|
|
|
|
/* Create a cancellable and a cancellable source for it. For this test,
|
|
|
|
|
* there’s no need to attach the source to a #GMainContext. */
|
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
|
cancellable_source = g_cancellable_source_new (cancellable);
|
|
|
|
|
g_source_set_callback (cancellable_source, G_SOURCE_FUNC (cancelled_cb), NULL, NULL);
|
|
|
|
|
|
tests: Speed up the cancellable test
The test added for #1841 spawned 100000 threads. That was fine on a
desktop machine, but on a heavily loaded CI machine, it could result in
large (and unpredictable) slowdowns, resulting in the test taking over
120s in about 1 in 5 runs, and hence failing that CI pipeline due to a
timeout. When passing normally on CI, the test would take around 90s.
Here’s a histogram of time per iteration on a failing (timed out) test
run. Each iteration is one thread spawn:
Iteration duration (µs) | Frequency
------------------------+----------
≤100 | 0
100–200 | 30257
200–400 | 13696
400–800 | 1046
800–1000 | 123
1000–2000 | 583
2000–4000 | 3779
4000–8000 | 4972
8000–10000 | 1027
10000–20000 | 2610
20000–40000 | 650
40000–80000 | 86
80000–100000 | 10
100000–200000 | 2
>200000 | 0
There’s no actual need for the test to spawn 100000 threads, so rewrite
it to reuse a single thread, and pass new data to that thread.
Reverting the original commit (e4a690f5dd95) reproduces the failure on
100 out of 100 test runs with this commit applied, so the test still
works.
The test now takes 3s, rather than 11s, to run on my computer, and has
passed when run with `meson test --repeat 1000 cancellable`.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-05-20 17:51:03 +02:00
|
|
|
|
/* Send it to the thread and wait until it’s ready to execute before
|
2020-02-21 15:44:44 +01:00
|
|
|
|
* cancelling our cancellable. */
|
tests: Speed up the cancellable test
The test added for #1841 spawned 100000 threads. That was fine on a
desktop machine, but on a heavily loaded CI machine, it could result in
large (and unpredictable) slowdowns, resulting in the test taking over
120s in about 1 in 5 runs, and hence failing that CI pipeline due to a
timeout. When passing normally on CI, the test would take around 90s.
Here’s a histogram of time per iteration on a failing (timed out) test
run. Each iteration is one thread spawn:
Iteration duration (µs) | Frequency
------------------------+----------
≤100 | 0
100–200 | 30257
200–400 | 13696
400–800 | 1046
800–1000 | 123
1000–2000 | 583
2000–4000 | 3779
4000–8000 | 4972
8000–10000 | 1027
10000–20000 | 2610
20000–40000 | 650
40000–80000 | 86
80000–100000 | 10
100000–200000 | 2
>200000 | 0
There’s no actual need for the test to spawn 100000 threads, so rewrite
it to reuse a single thread, and pass new data to that thread.
Reverting the original commit (e4a690f5dd95) reproduces the failure on
100 out of 100 test runs with this commit applied, so the test still
works.
The test now takes 3s, rather than 11s, to run on my computer, and has
passed when run with `meson test --repeat 1000 cancellable`.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-05-20 17:51:03 +02:00
|
|
|
|
g_async_queue_push (data.cancellable_source_queue, g_steal_pointer (&cancellable_source));
|
2020-02-21 15:44:44 +01:00
|
|
|
|
|
|
|
|
|
/* Race with disposal of the cancellable source. */
|
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
|
|
|
|
2020-06-17 19:06:20 +02:00
|
|
|
|
/* This thread can’t drop its reference to the #GCancellable here, as it
|
|
|
|
|
* might not be the final reference (depending on how the race is
|
|
|
|
|
* resolved: #GCancellableSource holds a strong ref on the #GCancellable),
|
|
|
|
|
* and at this point we can’t guarantee to support disposing of a
|
|
|
|
|
* #GCancellable in a different thread from where it’s created, especially
|
|
|
|
|
* when signal handlers are connected to it.
|
|
|
|
|
*
|
|
|
|
|
* So this is a workaround for a disposal-in-another-thread bug for
|
|
|
|
|
* #GCancellable, but there’s no hope of debugging and resolving it with
|
|
|
|
|
* this test setup, and the bug is orthogonal to what’s being tested here
|
|
|
|
|
* (a race between #GCancellable and #GCancellableSource). */
|
|
|
|
|
g_ptr_array_add (cancellables_pending_unref, g_steal_pointer (&cancellable));
|
2020-02-21 15:44:44 +01:00
|
|
|
|
}
|
tests: Speed up the cancellable test
The test added for #1841 spawned 100000 threads. That was fine on a
desktop machine, but on a heavily loaded CI machine, it could result in
large (and unpredictable) slowdowns, resulting in the test taking over
120s in about 1 in 5 runs, and hence failing that CI pipeline due to a
timeout. When passing normally on CI, the test would take around 90s.
Here’s a histogram of time per iteration on a failing (timed out) test
run. Each iteration is one thread spawn:
Iteration duration (µs) | Frequency
------------------------+----------
≤100 | 0
100–200 | 30257
200–400 | 13696
400–800 | 1046
800–1000 | 123
1000–2000 | 583
2000–4000 | 3779
4000–8000 | 4972
8000–10000 | 1027
10000–20000 | 2610
20000–40000 | 650
40000–80000 | 86
80000–100000 | 10
100000–200000 | 2
>200000 | 0
There’s no actual need for the test to spawn 100000 threads, so rewrite
it to reuse a single thread, and pass new data to that thread.
Reverting the original commit (e4a690f5dd95) reproduces the failure on
100 out of 100 test runs with this commit applied, so the test still
works.
The test now takes 3s, rather than 11s, to run on my computer, and has
passed when run with `meson test --repeat 1000 cancellable`.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-05-20 17:51:03 +02:00
|
|
|
|
|
|
|
|
|
/* Indicate that the test has finished. Can’t use %NULL as #GAsyncQueue
|
|
|
|
|
* doesn’t allow that.*/
|
|
|
|
|
g_async_queue_push (data.cancellable_source_queue, (gpointer) 1);
|
|
|
|
|
|
|
|
|
|
g_thread_join (g_steal_pointer (&thread));
|
|
|
|
|
|
|
|
|
|
g_assert (g_async_queue_length (data.cancellable_source_queue) == 0);
|
|
|
|
|
g_async_queue_unref (data.cancellable_source_queue);
|
|
|
|
|
g_mutex_clear (&data.mutex);
|
|
|
|
|
g_cond_clear (&data.cond);
|
2020-06-17 19:06:20 +02:00
|
|
|
|
|
|
|
|
|
g_ptr_array_unref (cancellables_pending_unref);
|
2020-02-21 15:44:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-20 20:53:26 +02:00
|
|
|
|
static void
|
|
|
|
|
test_cancellable_poll_fd (void)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable;
|
|
|
|
|
GPollFD pollfd = {.fd = -1};
|
|
|
|
|
int fd = -1;
|
|
|
|
|
|
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
|
g_test_skip ("Platform not supported");
|
|
|
|
|
return;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
|
|
|
|
|
|
g_assert_true (g_cancellable_make_pollfd (cancellable, &pollfd));
|
|
|
|
|
g_assert_cmpint (pollfd.fd, >, 0);
|
|
|
|
|
|
|
|
|
|
fd = g_cancellable_get_fd (cancellable);
|
|
|
|
|
g_assert_cmpint (fd, >, 0);
|
|
|
|
|
|
|
|
|
|
g_cancellable_release_fd (cancellable);
|
|
|
|
|
g_cancellable_release_fd (cancellable);
|
|
|
|
|
|
|
|
|
|
g_object_unref (cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_cancellable_cancelled_poll_fd (void)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable;
|
|
|
|
|
GPollFD pollfd;
|
|
|
|
|
|
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
|
g_test_skip ("Platform not supported");
|
|
|
|
|
return;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
g_test_summary ("Tests that cancellation wakes up a pollable FD on creation");
|
|
|
|
|
|
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
|
g_assert_true (g_cancellable_make_pollfd (cancellable, &pollfd));
|
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
|
|
|
|
|
|
|
|
g_poll (&pollfd, 1, -1);
|
|
|
|
|
|
|
|
|
|
g_cancellable_release_fd (cancellable);
|
|
|
|
|
g_object_unref (cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
GCancellable *cancellable;
|
|
|
|
|
gboolean polling_started; /* Atomic */
|
|
|
|
|
} CancellablePollThreadData;
|
|
|
|
|
|
|
|
|
|
static gpointer
|
|
|
|
|
cancel_cancellable_thread (gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
CancellablePollThreadData *thread_data = user_data;
|
|
|
|
|
|
|
|
|
|
while (!g_atomic_int_get (&thread_data->polling_started))
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
/* Let's just wait a moment before cancelling, this is not really needed
|
|
|
|
|
* but we do it to simulate that the thread is actually doing something.
|
|
|
|
|
*/
|
|
|
|
|
g_usleep (G_USEC_PER_SEC / 10);
|
|
|
|
|
g_cancellable_cancel (thread_data->cancellable);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gpointer
|
|
|
|
|
polling_cancelled_cancellable_thread (gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
CancellablePollThreadData *thread_data = user_data;
|
|
|
|
|
GPollFD pollfd;
|
|
|
|
|
|
|
|
|
|
g_assert_true (g_cancellable_make_pollfd (thread_data->cancellable, &pollfd));
|
|
|
|
|
g_atomic_int_set (&thread_data->polling_started, TRUE);
|
|
|
|
|
|
|
|
|
|
g_poll (&pollfd, 1, -1);
|
|
|
|
|
|
|
|
|
|
g_cancellable_release_fd (thread_data->cancellable);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_cancellable_cancelled_poll_fd_threaded (void)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable;
|
|
|
|
|
CancellablePollThreadData thread_data = {0};
|
|
|
|
|
GThread *polling_thread = NULL;
|
|
|
|
|
GThread *cancelling_thread = NULL;
|
|
|
|
|
GPollFD pollfd;
|
|
|
|
|
|
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
|
g_test_skip ("Platform not supported");
|
|
|
|
|
return;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
g_test_summary ("Tests that a cancellation wakes up a pollable FD");
|
|
|
|
|
|
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
|
g_assert_true (g_cancellable_make_pollfd (cancellable, &pollfd));
|
|
|
|
|
|
|
|
|
|
thread_data.cancellable = cancellable;
|
|
|
|
|
|
|
|
|
|
polling_thread = g_thread_new ("/cancellable/poll-fd-cancelled-threaded/polling",
|
|
|
|
|
polling_cancelled_cancellable_thread,
|
|
|
|
|
&thread_data);
|
|
|
|
|
cancelling_thread = g_thread_new ("/cancellable/poll-fd-cancelled-threaded/cancelling",
|
|
|
|
|
cancel_cancellable_thread, &thread_data);
|
|
|
|
|
|
|
|
|
|
g_poll (&pollfd, 1, -1);
|
|
|
|
|
g_assert_true (g_cancellable_is_cancelled (cancellable));
|
|
|
|
|
g_cancellable_release_fd (cancellable);
|
|
|
|
|
|
|
|
|
|
g_thread_join (g_steal_pointer (&cancelling_thread));
|
|
|
|
|
g_thread_join (g_steal_pointer (&polling_thread));
|
|
|
|
|
|
|
|
|
|
g_object_unref (cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
GMainLoop *loop;
|
|
|
|
|
GCancellable *cancellable;
|
|
|
|
|
GCallback callback;
|
|
|
|
|
gboolean is_disconnecting;
|
2022-06-22 03:44:20 +02:00
|
|
|
|
gboolean is_resetting;
|
2022-08-03 20:33:21 +02:00
|
|
|
|
gpointer handler_id;
|
2022-06-20 20:53:26 +02:00
|
|
|
|
} ConnectingThreadData;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
on_cancellable_connect_disconnect (GCancellable *cancellable,
|
|
|
|
|
ConnectingThreadData *data)
|
|
|
|
|
{
|
2022-08-03 20:38:59 +02:00
|
|
|
|
gulong handler_id = (gulong) (guintptr) g_atomic_pointer_exchange (&data->handler_id, 0);
|
2022-06-20 20:53:26 +02:00
|
|
|
|
g_atomic_int_set (&data->is_disconnecting, TRUE);
|
|
|
|
|
g_cancellable_disconnect (cancellable, handler_id);
|
|
|
|
|
g_atomic_int_set (&data->is_disconnecting, FALSE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gpointer
|
|
|
|
|
connecting_thread (gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GMainContext *context;
|
|
|
|
|
ConnectingThreadData *data = user_data;
|
|
|
|
|
gulong handler_id;
|
|
|
|
|
GMainLoop *loop;
|
|
|
|
|
|
|
|
|
|
handler_id =
|
|
|
|
|
g_cancellable_connect (data->cancellable, data->callback, data, NULL);
|
|
|
|
|
|
|
|
|
|
context = g_main_context_new ();
|
|
|
|
|
g_main_context_push_thread_default (context);
|
|
|
|
|
loop = g_main_loop_new (context, FALSE);
|
|
|
|
|
|
2022-08-03 20:38:59 +02:00
|
|
|
|
g_atomic_pointer_set (&data->handler_id, (gpointer) (guintptr) handler_id);
|
2022-06-20 20:53:26 +02:00
|
|
|
|
g_atomic_pointer_set (&data->loop, loop);
|
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
|
|
|
|
|
|
g_main_context_pop_thread_default (context);
|
|
|
|
|
g_main_context_unref (context);
|
|
|
|
|
g_main_loop_unref (loop);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_cancellable_disconnect_on_cancelled_callback_hangs (void)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable;
|
|
|
|
|
GThread *thread = NULL;
|
|
|
|
|
GThread *cancelling_thread = NULL;
|
|
|
|
|
ConnectingThreadData thread_data = {0};
|
|
|
|
|
GMainLoop *thread_loop;
|
|
|
|
|
gpointer waited;
|
|
|
|
|
|
|
|
|
|
/* While this is not convenient, it's done to ensure that we don't have a
|
|
|
|
|
* race when trying to cancelling a cancellable that is about to be cancelled
|
|
|
|
|
* in another thread
|
|
|
|
|
*/
|
|
|
|
|
g_test_summary ("Tests that trying to disconnect a cancellable from the "
|
|
|
|
|
"cancelled signal callback will result in a deadlock "
|
|
|
|
|
"as per #GCancellable::cancelled");
|
|
|
|
|
|
|
|
|
|
if (!g_test_undefined ())
|
|
|
|
|
{
|
|
|
|
|
g_test_skip ("Skipping testing disallowed behaviour of disconnecting from "
|
|
|
|
|
"a cancellable from its cancelled callback");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-20 13:58:59 +02:00
|
|
|
|
/* Run the test in a subprocess. While we can get away with deadlocking a
|
2024-09-27 05:37:34 +02:00
|
|
|
|
* specific thread on Linux, the libc on FreeBSD manages to detect the
|
2024-08-20 13:58:59 +02:00
|
|
|
|
* deadlock and aborts the whole test process. */
|
|
|
|
|
if (!g_test_subprocess ())
|
|
|
|
|
{
|
|
|
|
|
g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
|
|
|
|
|
if (!g_test_trap_has_passed ())
|
|
|
|
|
g_test_trap_assert_stderr ("*Unexpected error from C library during 'pthread_mutex_lock': Resource deadlock avoided. Aborting.*");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-20 20:53:26 +02:00
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
|
thread_data.cancellable = cancellable;
|
|
|
|
|
thread_data.callback = G_CALLBACK (on_cancellable_connect_disconnect);
|
|
|
|
|
|
|
|
|
|
g_assert_false (g_atomic_int_get (&thread_data.is_disconnecting));
|
2022-08-03 20:38:59 +02:00
|
|
|
|
g_assert_cmpuint ((gulong) (guintptr) g_atomic_pointer_get (&thread_data.handler_id), ==, 0);
|
2022-06-20 20:53:26 +02:00
|
|
|
|
|
|
|
|
|
thread = g_thread_new ("/cancellable/disconnect-on-cancelled-callback-hangs",
|
|
|
|
|
connecting_thread, &thread_data);
|
|
|
|
|
|
|
|
|
|
while (!g_atomic_pointer_get (&thread_data.loop))
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
thread_loop = thread_data.loop;
|
2022-08-03 20:38:59 +02:00
|
|
|
|
g_assert_cmpuint ((gulong) (guintptr) g_atomic_pointer_get (&thread_data.handler_id), !=, 0);
|
2022-06-20 20:53:26 +02:00
|
|
|
|
|
2024-08-20 13:58:59 +02:00
|
|
|
|
/* This thread will hang (at least that's what this test wants to ensure), but
|
|
|
|
|
* we can't stop it from the caller, unless we'll expose pthread_cancel() (and
|
|
|
|
|
* similar) to GLib. So it will keep hanging until the test subprocess exits.
|
2022-06-20 20:53:26 +02:00
|
|
|
|
*/
|
|
|
|
|
cancelling_thread = g_thread_new ("/cancellable/disconnect-on-cancelled-callback-hangs",
|
|
|
|
|
(GThreadFunc) g_cancellable_cancel,
|
|
|
|
|
cancellable);
|
|
|
|
|
|
|
|
|
|
while (!g_cancellable_is_cancelled (cancellable) ||
|
|
|
|
|
!g_atomic_int_get (&thread_data.is_disconnecting))
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
g_assert_true (g_atomic_int_get (&thread_data.is_disconnecting));
|
2022-08-03 20:38:59 +02:00
|
|
|
|
g_assert_cmpuint ((gulong) (guintptr) g_atomic_pointer_get (&thread_data.handler_id), ==, 0);
|
2022-06-20 20:53:26 +02:00
|
|
|
|
|
|
|
|
|
waited = &waited;
|
|
|
|
|
g_timeout_add_once (100, (GSourceOnceFunc) g_nullify_pointer, &waited);
|
|
|
|
|
while (waited != NULL)
|
|
|
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
|
|
|
|
|
|
g_assert_true (g_atomic_int_get (&thread_data.is_disconnecting));
|
|
|
|
|
|
|
|
|
|
g_main_loop_quit (thread_loop);
|
|
|
|
|
g_assert_true (g_atomic_int_get (&thread_data.is_disconnecting));
|
|
|
|
|
|
|
|
|
|
g_thread_join (g_steal_pointer (&thread));
|
|
|
|
|
g_thread_unref (cancelling_thread);
|
|
|
|
|
g_object_unref (cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-22 03:44:20 +02:00
|
|
|
|
static void
|
|
|
|
|
on_cancelled_reset (GCancellable *cancellable,
|
|
|
|
|
gpointer data)
|
|
|
|
|
{
|
|
|
|
|
ConnectingThreadData *thread_data = data;
|
|
|
|
|
|
|
|
|
|
g_assert_true (g_cancellable_is_cancelled (cancellable));
|
|
|
|
|
g_atomic_int_set (&thread_data->is_resetting, TRUE);
|
|
|
|
|
g_cancellable_reset (cancellable);
|
|
|
|
|
g_assert_false (g_cancellable_is_cancelled (cancellable));
|
|
|
|
|
g_atomic_int_set (&thread_data->is_resetting, TRUE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_cancellable_reset_on_cancelled_callback_hangs (void)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable;
|
|
|
|
|
GThread *thread = NULL;
|
|
|
|
|
GThread *cancelling_thread = NULL;
|
|
|
|
|
ConnectingThreadData thread_data = {0};
|
|
|
|
|
GMainLoop *thread_loop;
|
|
|
|
|
gpointer waited;
|
|
|
|
|
|
|
|
|
|
/* While this is not convenient, it's done to ensure that we don't have a
|
|
|
|
|
* race when trying to cancelling a cancellable that is about to be cancelled
|
|
|
|
|
* in another thread
|
|
|
|
|
*/
|
|
|
|
|
g_test_summary ("Tests that trying to reset a cancellable from the "
|
|
|
|
|
"cancelled signal callback will result in a deadlock "
|
|
|
|
|
"as per #GCancellable::cancelled");
|
|
|
|
|
|
|
|
|
|
if (!g_test_undefined ())
|
|
|
|
|
{
|
|
|
|
|
g_test_skip ("Skipping testing disallowed behaviour of resetting a "
|
|
|
|
|
"cancellable from its callback");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-20 13:58:59 +02:00
|
|
|
|
/* Run the test in a subprocess. While we can get away with deadlocking a
|
2024-09-27 05:37:34 +02:00
|
|
|
|
* specific thread on Linux, the libc on FreeBSD manages to detect the
|
2024-08-20 13:58:59 +02:00
|
|
|
|
* deadlock and aborts the whole test process. */
|
|
|
|
|
if (!g_test_subprocess ())
|
|
|
|
|
{
|
|
|
|
|
g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
|
|
|
|
|
if (!g_test_trap_has_passed ())
|
|
|
|
|
g_test_trap_assert_stderr ("*Unexpected error from C library during 'pthread_mutex_lock': Resource deadlock avoided. Aborting.*");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-22 03:44:20 +02:00
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
|
thread_data.cancellable = cancellable;
|
|
|
|
|
thread_data.callback = G_CALLBACK (on_cancelled_reset);
|
|
|
|
|
|
|
|
|
|
g_assert_false (g_atomic_int_get (&thread_data.is_resetting));
|
2022-08-03 20:38:59 +02:00
|
|
|
|
g_assert_cmpuint ((gulong) (guintptr) g_atomic_pointer_get (&thread_data.handler_id), ==, 0);
|
2022-06-22 03:44:20 +02:00
|
|
|
|
|
|
|
|
|
thread = g_thread_new ("/cancellable/reset-on-cancelled-callback-hangs",
|
|
|
|
|
connecting_thread, &thread_data);
|
|
|
|
|
|
|
|
|
|
while (!g_atomic_pointer_get (&thread_data.loop))
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
thread_loop = thread_data.loop;
|
2022-08-03 20:38:59 +02:00
|
|
|
|
g_assert_cmpuint ((gulong) (guintptr) g_atomic_pointer_get (&thread_data.handler_id), !=, 0);
|
2022-06-22 03:44:20 +02:00
|
|
|
|
|
2024-08-20 13:58:59 +02:00
|
|
|
|
/* This thread will hang (at least that's what this test wants to ensure), but
|
|
|
|
|
* we can't stop it from the caller, unless we'll expose pthread_cancel() (and
|
|
|
|
|
* similar) to GLib. So it will keep hanging until the test subprocess exits.
|
2022-06-22 03:44:20 +02:00
|
|
|
|
*/
|
|
|
|
|
cancelling_thread = g_thread_new ("/cancellable/reset-on-cancelled-callback-hangs",
|
|
|
|
|
(GThreadFunc) g_cancellable_cancel,
|
|
|
|
|
cancellable);
|
|
|
|
|
|
|
|
|
|
while (!g_cancellable_is_cancelled (cancellable) ||
|
|
|
|
|
!g_atomic_int_get (&thread_data.is_resetting))
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
g_assert_true (g_atomic_int_get (&thread_data.is_resetting));
|
2022-08-03 20:38:59 +02:00
|
|
|
|
g_assert_cmpuint ((gulong) (guintptr) g_atomic_pointer_get (&thread_data.handler_id), >, 0);
|
2022-06-22 03:44:20 +02:00
|
|
|
|
|
|
|
|
|
waited = &waited;
|
|
|
|
|
g_timeout_add_once (100, (GSourceOnceFunc) g_nullify_pointer, &waited);
|
|
|
|
|
while (waited != NULL)
|
|
|
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
|
|
|
|
|
|
g_assert_true (g_atomic_int_get (&thread_data.is_resetting));
|
|
|
|
|
|
|
|
|
|
g_main_loop_quit (thread_loop);
|
|
|
|
|
g_assert_true (g_atomic_int_get (&thread_data.is_resetting));
|
|
|
|
|
|
|
|
|
|
g_thread_join (g_steal_pointer (&thread));
|
|
|
|
|
g_thread_unref (cancelling_thread);
|
|
|
|
|
g_object_unref (cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-22 00:37:22 +02:00
|
|
|
|
static gpointer
|
|
|
|
|
repeatedly_cancelling_thread (gpointer data)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable = data;
|
|
|
|
|
const guint iterations = 10000;
|
|
|
|
|
|
|
|
|
|
for (guint i = 0; i < iterations; ++i)
|
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gpointer
|
|
|
|
|
repeatedly_resetting_thread (gpointer data)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable = data;
|
|
|
|
|
const guint iterations = 10000;
|
|
|
|
|
|
|
|
|
|
for (guint i = 0; i < iterations; ++i)
|
|
|
|
|
g_cancellable_reset (cancellable);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
on_racy_cancellable_cancelled (GCancellable *cancellable,
|
|
|
|
|
gpointer data)
|
|
|
|
|
{
|
|
|
|
|
gboolean *callback_called = data;
|
|
|
|
|
|
|
|
|
|
g_assert_true (g_cancellable_is_cancelled (cancellable));
|
|
|
|
|
g_atomic_int_set (callback_called, TRUE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_cancellable_cancel_reset_races (void)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable;
|
|
|
|
|
GThread *resetting_thread = NULL;
|
|
|
|
|
GThread *cancelling_thread = NULL;
|
|
|
|
|
gboolean callback_called = FALSE;
|
|
|
|
|
|
|
|
|
|
g_test_summary ("Tests threads racing for cancelling and resetting a GCancellable");
|
|
|
|
|
|
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
|
|
|
|
|
|
g_cancellable_connect (cancellable, G_CALLBACK (on_racy_cancellable_cancelled),
|
|
|
|
|
&callback_called, NULL);
|
|
|
|
|
g_assert_false (callback_called);
|
|
|
|
|
|
|
|
|
|
resetting_thread = g_thread_new ("/cancellable/cancel-reset-races/resetting",
|
|
|
|
|
repeatedly_resetting_thread,
|
|
|
|
|
cancellable);
|
|
|
|
|
cancelling_thread = g_thread_new ("/cancellable/cancel-reset-races/cancelling",
|
|
|
|
|
repeatedly_cancelling_thread, cancellable);
|
|
|
|
|
|
|
|
|
|
g_thread_join (g_steal_pointer (&cancelling_thread));
|
|
|
|
|
g_thread_join (g_steal_pointer (&resetting_thread));
|
|
|
|
|
|
|
|
|
|
g_assert_true (callback_called);
|
|
|
|
|
|
|
|
|
|
g_object_unref (cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-22 01:30:57 +02:00
|
|
|
|
static gpointer
|
|
|
|
|
repeatedly_connecting_thread (gpointer data)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable = data;
|
|
|
|
|
const guint iterations = 10000;
|
|
|
|
|
gboolean callback_ever_called = FALSE;
|
|
|
|
|
|
|
|
|
|
for (guint i = 0; i < iterations; ++i)
|
|
|
|
|
{
|
|
|
|
|
gboolean callback_called = FALSE;
|
|
|
|
|
gboolean called;
|
|
|
|
|
gulong id = g_cancellable_connect (cancellable,
|
|
|
|
|
G_CALLBACK (on_racy_cancellable_cancelled),
|
|
|
|
|
&callback_called, NULL);
|
|
|
|
|
called = g_atomic_int_get (&callback_called);
|
|
|
|
|
callback_ever_called |= called;
|
|
|
|
|
if (g_test_verbose () && called)
|
|
|
|
|
g_test_message ("Reconnecting cancellation callback called");
|
|
|
|
|
g_cancellable_disconnect (cancellable, id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!callback_ever_called)
|
|
|
|
|
g_test_incomplete ("We didn't really checked if callbacks is called properly");
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_cancellable_cancel_reset_connect_races (void)
|
|
|
|
|
{
|
|
|
|
|
GCancellable *cancellable;
|
|
|
|
|
GThread *resetting_thread = NULL;
|
|
|
|
|
GThread *cancelling_thread = NULL;
|
|
|
|
|
GThread *connecting_thread = NULL;
|
|
|
|
|
gboolean callback_called = FALSE;
|
|
|
|
|
|
|
|
|
|
g_test_summary ("Tests threads racing for cancelling, connecting and disconnecting "
|
|
|
|
|
" and resetting a GCancellable");
|
|
|
|
|
|
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
|
|
|
|
|
|
g_cancellable_connect (cancellable, G_CALLBACK (on_racy_cancellable_cancelled),
|
|
|
|
|
&callback_called, NULL);
|
|
|
|
|
g_assert_false (callback_called);
|
|
|
|
|
|
|
|
|
|
resetting_thread = g_thread_new ("/cancel-reset-connect-races/resetting",
|
|
|
|
|
repeatedly_resetting_thread,
|
|
|
|
|
cancellable);
|
|
|
|
|
cancelling_thread = g_thread_new ("/cancel-reset-connect-races/cancelling",
|
|
|
|
|
repeatedly_cancelling_thread, cancellable);
|
|
|
|
|
connecting_thread = g_thread_new ("/cancel-reset-connect-races/connecting",
|
|
|
|
|
repeatedly_connecting_thread, cancellable);
|
|
|
|
|
|
|
|
|
|
g_thread_join (g_steal_pointer (&cancelling_thread));
|
|
|
|
|
g_thread_join (g_steal_pointer (&resetting_thread));
|
|
|
|
|
g_thread_join (g_steal_pointer (&connecting_thread));
|
|
|
|
|
|
|
|
|
|
g_assert_true (callback_called);
|
|
|
|
|
|
|
|
|
|
g_object_unref (cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-21 23:07:37 +02:00
|
|
|
|
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
|
|
|
|
|
do_nothing (G_GNUC_UNUSED void *user_data)
|
|
|
|
|
{
|
|
|
|
|
/* An empty timeout/idle once callback function */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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, do_nothing, 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);
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-12 11:49:31 +02:00
|
|
|
|
int
|
|
|
|
|
main (int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
|
|
|
|
|
|
g_test_add_func ("/cancellable/multiple-concurrent", test_cancel_multiple_concurrent);
|
2019-03-05 16:44:16 +01:00
|
|
|
|
g_test_add_func ("/cancellable/null", test_cancel_null);
|
2022-06-20 20:53:26 +02:00
|
|
|
|
g_test_add_func ("/cancellable/disconnect-on-cancelled-callback-hangs", test_cancellable_disconnect_on_cancelled_callback_hangs);
|
2022-06-22 03:44:20 +02:00
|
|
|
|
g_test_add_func ("/cancellable/resets-on-cancel-callback-hangs", test_cancellable_reset_on_cancelled_callback_hangs);
|
2022-06-20 20:53:26 +02:00
|
|
|
|
g_test_add_func ("/cancellable/poll-fd", test_cancellable_poll_fd);
|
|
|
|
|
g_test_add_func ("/cancellable/poll-fd-cancelled", test_cancellable_cancelled_poll_fd);
|
|
|
|
|
g_test_add_func ("/cancellable/poll-fd-cancelled-threaded", test_cancellable_cancelled_poll_fd_threaded);
|
2022-06-22 00:37:22 +02:00
|
|
|
|
g_test_add_func ("/cancellable/cancel-reset-races", test_cancellable_cancel_reset_races);
|
2022-06-22 01:30:57 +02:00
|
|
|
|
g_test_add_func ("/cancellable/cancel-reset-connect-races", test_cancellable_cancel_reset_connect_races);
|
2020-02-21 15:44:44 +01:00
|
|
|
|
g_test_add_func ("/cancellable-source/threaded-dispose", test_cancellable_source_threaded_dispose);
|
2022-06-21 23:07:37 +02:00
|
|
|
|
g_test_add_func ("/cancellable-source/can-be-fired-multiple-times", test_cancellable_source_can_be_fired_multiple_times);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
|
|
|
|
|
return g_test_run ();
|
|
|
|
|
}
|