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 (e4a690f5dd) 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>
This commit is contained in:
Philip Withnall 2020-05-20 16:51:03 +01:00
parent e816e9c86f
commit 9f27f49e34

View File

@ -232,7 +232,8 @@ typedef struct
{ {
GCond cond; GCond cond;
GMutex mutex; GMutex mutex;
GSource *cancellable_source; /* (owned) */ gboolean thread_ready;
GAsyncQueue *cancellable_source_queue; /* (owned) (element-type GCancellableSource) */
} ThreadedDisposeData; } ThreadedDisposeData;
static gboolean static gboolean
@ -247,14 +248,18 @@ static gpointer
threaded_dispose_thread_cb (gpointer user_data) threaded_dispose_thread_cb (gpointer user_data)
{ {
ThreadedDisposeData *data = user_data; ThreadedDisposeData *data = user_data;
GSource *cancellable_source;
/* Synchronise with the main thread before trying to reproduce the race. */
g_mutex_lock (&data->mutex); g_mutex_lock (&data->mutex);
data->thread_ready = TRUE;
g_cond_broadcast (&data->cond); g_cond_broadcast (&data->cond);
g_mutex_unlock (&data->mutex); g_mutex_unlock (&data->mutex);
while ((cancellable_source = g_async_queue_pop (data->cancellable_source_queue)) != (gpointer) 1)
{
/* Race with cancellation of the cancellable. */ /* Race with cancellation of the cancellable. */
g_source_unref (data->cancellable_source); g_source_unref (cancellable_source);
}
return NULL; return NULL;
} }
@ -262,6 +267,8 @@ threaded_dispose_thread_cb (gpointer user_data)
static void static void
test_cancellable_source_threaded_dispose (void) test_cancellable_source_threaded_dispose (void)
{ {
ThreadedDisposeData data;
GThread *thread = NULL;
guint i; guint i;
g_test_summary ("Test a thread race between disposing of a GCancellableSource " g_test_summary ("Test a thread race between disposing of a GCancellableSource "
@ -269,12 +276,25 @@ test_cancellable_source_threaded_dispose (void)
"to (in another thread)"); "to (in another thread)");
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/1841"); g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/1841");
/* Create a new thread and wait until its 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);
for (i = 0; i < 100000; i++) for (i = 0; i < 100000; i++)
{ {
GCancellable *cancellable = NULL; GCancellable *cancellable = NULL;
GSource *cancellable_source = NULL; GSource *cancellable_source = NULL;
ThreadedDisposeData data;
GThread *thread = NULL;
/* Create a cancellable and a cancellable source for it. For this test, /* Create a cancellable and a cancellable source for it. For this test,
* theres no need to attach the source to a #GMainContext. */ * theres no need to attach the source to a #GMainContext. */
@ -282,26 +302,26 @@ test_cancellable_source_threaded_dispose (void)
cancellable_source = g_cancellable_source_new (cancellable); cancellable_source = g_cancellable_source_new (cancellable);
g_source_set_callback (cancellable_source, G_SOURCE_FUNC (cancelled_cb), NULL, NULL); g_source_set_callback (cancellable_source, G_SOURCE_FUNC (cancelled_cb), NULL, NULL);
/* Create a new thread and wait until its ready to execute before /* Send it to the thread and wait until its ready to execute before
* cancelling our cancellable. */ * cancelling our cancellable. */
g_cond_init (&data.cond); g_async_queue_push (data.cancellable_source_queue, g_steal_pointer (&cancellable_source));
g_mutex_init (&data.mutex);
data.cancellable_source = g_steal_pointer (&cancellable_source);
g_mutex_lock (&data.mutex);
thread = g_thread_new ("/cancellable-source/threaded-dispose",
threaded_dispose_thread_cb, &data);
g_cond_wait (&data.cond, &data.mutex);
g_mutex_unlock (&data.mutex);
/* Race with disposal of the cancellable source. */ /* Race with disposal of the cancellable source. */
g_cancellable_cancel (cancellable); g_cancellable_cancel (cancellable);
g_thread_join (g_steal_pointer (&thread));
g_mutex_clear (&data.mutex);
g_cond_clear (&data.cond);
g_object_unref (cancellable); g_object_unref (cancellable);
} }
/* Indicate that the test has finished. Cant use %NULL as #GAsyncQueue
* doesnt 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);
} }
int int