1 /* Unit tests for GThreadPool
2  * Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
3  *
4  * This work is provided "as is"; redistribution and modification
5  * in whole or in part, in any medium, physical or electronic is
6  * permitted without restriction.
7  *
8  * This work is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11  *
12  * In no event shall the authors or contributors be liable for any
13  * direct, indirect, incidental, special, exemplary, or consequential
14  * damages (including, but not limited to, procurement of substitute
15  * goods or services; loss of use, data, or profits; or business
16  * interruption) however caused and on any theory of liability, whether
17  * in contract, strict liability, or tort (including negligence or
18  * otherwise) arising in any way out of the use of this software, even
19  * if advised of the possibility of such damage.
20  */
21 
22 #include <config.h>
23 
24 #include <glib.h>
25 
26 typedef struct {
27   GMutex mutex;
28   GCond cond;
29   gboolean signalled;
30 } MutexCond;
31 
32 static void
pool_func(gpointer data,gpointer user_data)33 pool_func (gpointer data, gpointer user_data)
34 {
35   MutexCond *m = user_data;
36 
37   g_mutex_lock (&m->mutex);
38   g_assert_false (m->signalled);
39   g_assert_true (data == GUINT_TO_POINTER (123));
40   m->signalled = TRUE;
41   g_cond_signal (&m->cond);
42   g_mutex_unlock (&m->mutex);
43 }
44 
45 static void
test_simple(gconstpointer shared)46 test_simple (gconstpointer shared)
47 {
48   GThreadPool *pool;
49   GError *err = NULL;
50   MutexCond m;
51   gboolean success;
52 
53   g_mutex_init (&m.mutex);
54   g_cond_init (&m.cond);
55 
56   if (GPOINTER_TO_INT (shared))
57     {
58       g_test_summary ("Tests that a shared, non-exclusive thread pool "
59                       "generally works.");
60       pool = g_thread_pool_new (pool_func, &m, -1, FALSE, &err);
61     }
62   else
63     {
64       g_test_summary ("Tests that an exclusive thread pool generally works.");
65       pool = g_thread_pool_new (pool_func, &m, 2, TRUE, &err);
66     }
67   g_assert_no_error (err);
68   g_assert_nonnull (pool);
69 
70   g_mutex_lock (&m.mutex);
71   m.signalled = FALSE;
72 
73   success = g_thread_pool_push (pool, GUINT_TO_POINTER (123), &err);
74   g_assert_no_error (err);
75   g_assert_true (success);
76 
77   while (!m.signalled)
78     g_cond_wait (&m.cond, &m.mutex);
79   g_mutex_unlock (&m.mutex);
80 
81   g_thread_pool_free (pool, TRUE, TRUE);
82 }
83 
84 static void
dummy_pool_func(gpointer data,gpointer user_data)85 dummy_pool_func (gpointer data, gpointer user_data)
86 {
87   g_assert_true (data == GUINT_TO_POINTER (123));
88 }
89 
90 static void
test_create_first_pool(gconstpointer shared_first)91 test_create_first_pool (gconstpointer shared_first)
92 {
93   GThreadPool *pool;
94   GError *err = NULL;
95   gboolean success;
96 
97   g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2012");
98   if (GPOINTER_TO_INT (shared_first))
99     {
100       g_test_summary ("Tests that creating an exclusive pool after a "
101                       "shared one works.");
102     }
103   else
104     {
105       g_test_summary ("Tests that creating a shared pool after an "
106                       "exclusive one works.");
107     }
108 
109   if (!g_test_subprocess ())
110     {
111       g_test_trap_subprocess (NULL, 0, 0);
112       g_test_trap_assert_passed ();
113       return;
114     }
115 
116   g_thread_pool_set_max_unused_threads (0);
117 
118   if (GPOINTER_TO_INT (shared_first))
119     pool = g_thread_pool_new (dummy_pool_func, NULL, -1, FALSE, &err);
120   else
121     pool = g_thread_pool_new (dummy_pool_func, NULL, 2, TRUE, &err);
122   g_assert_no_error (err);
123   g_assert_nonnull (pool);
124 
125   success = g_thread_pool_push (pool, GUINT_TO_POINTER (123), &err);
126   g_assert_no_error (err);
127   g_assert_true (success);
128 
129   g_thread_pool_free (pool, TRUE, TRUE);
130 
131   if (GPOINTER_TO_INT (shared_first))
132     pool = g_thread_pool_new (dummy_pool_func, NULL, 2, TRUE, &err);
133   else
134     pool = g_thread_pool_new (dummy_pool_func, NULL, -1, FALSE, &err);
135   g_assert_no_error (err);
136   g_assert_nonnull (pool);
137 
138   success = g_thread_pool_push (pool, GUINT_TO_POINTER (123), &err);
139   g_assert_no_error (err);
140   g_assert_true (success);
141 
142   g_thread_pool_free (pool, TRUE, TRUE);
143 }
144 
145 typedef struct
146 {
147   GMutex mutex;  /* (owned) */
148   GCond cond;  /* (owned) */
149   gboolean threads_should_block;  /* protected by mutex, cond */
150 
151   guint n_jobs_started;  /* (atomic) */
152   guint n_jobs_completed;  /* (atomic) */
153   guint n_free_func_calls;  /* (atomic) */
154 } TestThreadPoolFullData;
155 
156 static void
full_thread_func(gpointer data,gpointer user_data)157 full_thread_func (gpointer data,
158                   gpointer user_data)
159 {
160   TestThreadPoolFullData *test_data = data;
161 
162   g_atomic_int_inc (&test_data->n_jobs_started);
163 
164   /* Make the thread block until told to stop blocking. */
165   g_mutex_lock (&test_data->mutex);
166   while (test_data->threads_should_block)
167     g_cond_wait (&test_data->cond, &test_data->mutex);
168   g_mutex_unlock (&test_data->mutex);
169 
170   g_atomic_int_inc (&test_data->n_jobs_completed);
171 }
172 
173 static void
free_func(gpointer user_data)174 free_func (gpointer user_data)
175 {
176   TestThreadPoolFullData *test_data = user_data;
177 
178   g_atomic_int_inc (&test_data->n_free_func_calls);
179 }
180 
181 static void
test_thread_pool_full(gconstpointer shared_first)182 test_thread_pool_full (gconstpointer shared_first)
183 {
184   guint i;
185 
186   g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/121");
187 
188   g_thread_pool_set_max_unused_threads (0);
189 
190   /* Run the test twice, once with a shared pool and once with an exclusive one. */
191   for (i = 0; i < 2; i++)
192     {
193       GThreadPool *pool;
194       TestThreadPoolFullData test_data;
195       GError *local_error = NULL;
196       gboolean success;
197       guint j;
198 
199       g_mutex_init (&test_data.mutex);
200       g_cond_init (&test_data.cond);
201       test_data.threads_should_block = TRUE;
202       test_data.n_jobs_started = 0;
203       test_data.n_jobs_completed = 0;
204       test_data.n_free_func_calls = 0;
205 
206       /* Create a thread pool with only one worker thread. The pool can be
207        * created in shared or exclusive mode. */
208       pool = g_thread_pool_new_full (full_thread_func, &test_data, free_func,
209                                      1, (i == 0),
210                                      &local_error);
211       g_assert_no_error (local_error);
212       g_assert_nonnull (pool);
213 
214       /* Push two jobs into the pool. The first one will start executing and
215        * will block, the second one will wait in the queue as there’s only one
216        * worker thread. */
217       for (j = 0; j < 2; j++)
218         {
219           success = g_thread_pool_push (pool, &test_data, &local_error);
220           g_assert_no_error (local_error);
221           g_assert_true (success);
222         }
223 
224       /* Wait for the first job to start. */
225       while (g_atomic_int_get (&test_data.n_jobs_started) == 0);
226 
227       /* Free the pool. This won’t actually free the queued second job yet, as
228        * the thread pool hangs around until the executing first job has
229        * completed. The first job will complete only once @threads_should_block
230        * is unset. */
231       g_thread_pool_free (pool, TRUE, FALSE);
232 
233       g_assert_cmpuint (g_atomic_int_get (&test_data.n_jobs_started), ==, 1);
234       g_assert_cmpuint (g_atomic_int_get (&test_data.n_jobs_completed), ==, 0);
235       g_assert_cmpuint (g_atomic_int_get (&test_data.n_free_func_calls), ==, 0);
236 
237       /* Unblock the job and allow the pool to be freed. */
238       g_mutex_lock (&test_data.mutex);
239       test_data.threads_should_block = FALSE;
240       g_cond_signal (&test_data.cond);
241       g_mutex_unlock (&test_data.mutex);
242 
243       /* Wait for the first job to complete before freeing the mutex and cond. */
244       while (g_atomic_int_get (&test_data.n_jobs_completed) != 1 ||
245              g_atomic_int_get (&test_data.n_free_func_calls) != 1);
246 
247       g_assert_cmpuint (g_atomic_int_get (&test_data.n_jobs_started), ==, 1);
248       g_assert_cmpuint (g_atomic_int_get (&test_data.n_jobs_completed), ==, 1);
249       g_assert_cmpuint (g_atomic_int_get (&test_data.n_free_func_calls), ==, 1);
250 
251       g_cond_clear (&test_data.cond);
252       g_mutex_clear (&test_data.mutex);
253     }
254 }
255 
256 int
main(int argc,char * argv[])257 main (int argc, char *argv[])
258 {
259   g_test_init (&argc, &argv, NULL);
260 
261   g_test_add_data_func ("/thread_pool/shared", GINT_TO_POINTER (TRUE), test_simple);
262   g_test_add_data_func ("/thread_pool/exclusive", GINT_TO_POINTER (FALSE), test_simple);
263   g_test_add_data_func ("/thread_pool/create_shared_after_exclusive", GINT_TO_POINTER (FALSE), test_create_first_pool);
264   g_test_add_data_func ("/thread_pool/create_full", NULL, test_thread_pool_full);
265   g_test_add_data_func ("/thread_pool/create_exclusive_after_shared", GINT_TO_POINTER (TRUE), test_create_first_pool);
266 
267   return g_test_run ();
268 }
269