2011-08-12 11:49:31 +02:00
|
|
|
|
/* GIO - GLib Input, Output and Streaming Library
|
|
|
|
|
*
|
|
|
|
|
* Copyright (C) 2011 Collabora Ltd.
|
|
|
|
|
*
|
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
|
* License as published by the Free Software Foundation; either
|
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)
|
|
|
|
|
{
|
2021-01-29 20:28:25 +01:00
|
|
|
|
#ifdef _GLIB_ADDRESS_SANITIZER
|
|
|
|
|
g_test_incomplete ("FIXME: Leaks lots of GCancellableSource objects, see glib#2309");
|
|
|
|
|
(void) cancelled_cb;
|
|
|
|
|
(void) threaded_dispose_thread_cb;
|
|
|
|
|
#else
|
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");
|
|
|
|
|
|
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);
|
2021-01-29 20:28:25 +01:00
|
|
|
|
#endif
|
2020-02-21 15:44:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
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);
|
2020-02-21 15:44:44 +01:00
|
|
|
|
g_test_add_func ("/cancellable-source/threaded-dispose", test_cancellable_source_threaded_dispose);
|
2011-08-12 11:49:31 +02:00
|
|
|
|
|
|
|
|
|
return g_test_run ();
|
|
|
|
|
}
|