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