1 /* Unit tests for GCond
2 * Copyright (C) 2011 Red Hat, Inc
3 * Author: Matthias Clasen
4 *
5 * SPDX-License-Identifier: LicenseRef-old-glib-tests
6 *
7 * This work is provided "as is"; redistribution and modification
8 * in whole or in part, in any medium, physical or electronic is
9 * permitted without restriction.
10 *
11 * This work is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 *
15 * In no event shall the authors or contributors be liable for any
16 * direct, indirect, incidental, special, exemplary, or consequential
17 * damages (including, but not limited to, procurement of substitute
18 * goods or services; loss of use, data, or profits; or business
19 * interruption) however caused and on any theory of liability, whether
20 * in contract, strict liability, or tort (including negligence or
21 * otherwise) arising in any way out of the use of this software, even
22 * if advised of the possibility of such damage.
23 */
24
25 /* We are testing some deprecated APIs here */
26 #ifndef GLIB_DISABLE_DEPRECATION_WARNINGS
27 #define GLIB_DISABLE_DEPRECATION_WARNINGS
28 #endif
29
30 #include <glib.h>
31
32 static GCond cond;
33 static GMutex mutex;
34 static gint next; /* locked by @mutex */
35
36 static void
37 push_value (gint value)
38 {
39 g_mutex_lock (&mutex);
40 while (next != 0)
41 g_cond_wait (&cond, &mutex);
42 next = value;
43 if (g_test_verbose ())
44 g_printerr ("Thread %p producing next value: %d\n", g_thread_self (), value);
45 if (value % 10 == 0)
46 g_cond_broadcast (&cond);
47 else
48 g_cond_signal (&cond);
49 g_mutex_unlock (&mutex);
50 }
51
52 static gint
53 pop_value (void)
54 {
55 gint value;
56
57 g_mutex_lock (&mutex);
58 while (next == 0)
59 {
60 if (g_test_verbose ())
61 g_printerr ("Thread %p waiting for cond\n", g_thread_self ());
62 g_cond_wait (&cond, &mutex);
63 }
64 value = next;
65 next = 0;
66 g_cond_broadcast (&cond);
67 if (g_test_verbose ())
68 g_printerr ("Thread %p consuming value %d\n", g_thread_self (), value);
69 g_mutex_unlock (&mutex);
70
71 return value;
72 }
73
74 static gpointer
75 produce_values (gpointer data)
76 {
77 gint total;
78 gint i;
79
80 total = 0;
81
82 for (i = 1; i < 100; i++)
83 {
84 total += i;
85 push_value (i);
86 }
87
88 push_value (-1);
89 push_value (-1);
90
91 if (g_test_verbose ())
92 g_printerr ("Thread %p produced %d altogether\n", g_thread_self (), total);
93
94 return GINT_TO_POINTER (total);
95 }
96
97 static gpointer
98 consume_values (gpointer data)
99 {
100 gint accum = 0;
101 gint value;
102
103 while (TRUE)
104 {
105 value = pop_value ();
106 if (value == -1)
107 break;
108
109 accum += value;
110 }
111
112 if (g_test_verbose ())
113 g_printerr ("Thread %p accumulated %d\n", g_thread_self (), accum);
114
115 return GINT_TO_POINTER (accum);
116 }
117
118 static GThread *producer, *consumer1, *consumer2;
119
120 static void
121 test_cond1 (void)
122 {
123 gint total, acc1, acc2;
124
125 producer = g_thread_create (produce_values, NULL, TRUE, NULL);
126 consumer1 = g_thread_create (consume_values, NULL, TRUE, NULL);
127 consumer2 = g_thread_create (consume_values, NULL, TRUE, NULL);
128
129 total = GPOINTER_TO_INT (g_thread_join (producer));
130 acc1 = GPOINTER_TO_INT (g_thread_join (consumer1));
131 acc2 = GPOINTER_TO_INT (g_thread_join (consumer2));
132
133 g_assert_cmpint (total, ==, acc1 + acc2);
134 }
135
136 typedef struct
137 {
138 GMutex mutex;
139 GCond cond;
140 gint limit;
141 gint count;
142 } Barrier;
143
144 static void
145 barrier_init (Barrier *barrier,
146 gint limit)
147 {
148 g_mutex_init (&barrier->mutex);
149 g_cond_init (&barrier->cond);
150 barrier->limit = limit;
151 barrier->count = limit;
152 }
153
154 static gint
155 barrier_wait (Barrier *barrier)
156 {
157 gint ret;
158
159 g_mutex_lock (&barrier->mutex);
160 barrier->count--;
161 if (barrier->count == 0)
162 {
163 ret = -1;
164 barrier->count = barrier->limit;
165 g_cond_broadcast (&barrier->cond);
166 }
167 else
168 {
169 ret = 0;
170 while (barrier->count != barrier->limit)
171 g_cond_wait (&barrier->cond, &barrier->mutex);
172 }
173 g_mutex_unlock (&barrier->mutex);
174
175 return ret;
176 }
177
178 static void
179 barrier_clear (Barrier *barrier)
180 {
181 g_mutex_clear (&barrier->mutex);
182 g_cond_clear (&barrier->cond);
183 }
184
185 static Barrier b;
186 static gint check;
187
188 static gpointer
189 cond2_func (gpointer data)
190 {
191 gint value = GPOINTER_TO_INT (data);
192 gint ret;
193
194 g_atomic_int_inc (&check);
195
196 if (g_test_verbose ())
197 g_printerr ("thread %d starting, check %d\n", value, g_atomic_int_get (&check));
198
199 g_usleep (10000 * value);
200
201 g_atomic_int_inc (&check);
202
203 if (g_test_verbose ())
204 g_printerr ("thread %d reaching barrier, check %d\n", value, g_atomic_int_get (&check));
205
206 ret = barrier_wait (&b);
207
208 g_assert_cmpint (g_atomic_int_get (&check), ==, 10);
209
210 if (g_test_verbose ())
211 g_printerr ("thread %d leaving barrier (%d), check %d\n", value, ret, g_atomic_int_get (&check));
212
213 return NULL;
214 }
215
216 /* this test demonstrates how to use a condition variable
217 * to implement a barrier
218 */
219 static void
220 test_cond2 (void)
221 {
222 gint i;
223 GThread *threads[5];
224
225 g_atomic_int_set (&check, 0);
226
227 barrier_init (&b, 5);
228 for (i = 0; i < 5; i++)
229 threads[i] = g_thread_create (cond2_func, GINT_TO_POINTER (i), TRUE, NULL);
230
231 for (i = 0; i < 5; i++)
232 g_thread_join (threads[i]);
233
234 g_assert_cmpint (g_atomic_int_get (&check), ==, 10);
235
236 barrier_clear (&b);
237 }
238
239 static void
240 test_wait_until (void)
241 {
242 gint64 until;
243 GMutex lock;
244 GCond local_cond;
245
246 /* This test will make sure we don't wait too much or too little.
247 *
248 * We check the 'too long' with a timeout of 60 seconds.
249 *
250 * We check the 'too short' by verifying a guarantee of the API: we
251 * should not wake up until the specified time has passed.
252 */
253 g_mutex_init (&lock);
254 g_cond_init (&local_cond);
255
256 until = g_get_monotonic_time () + G_TIME_SPAN_SECOND;
257
258 /* Could still have spurious wakeups, so we must loop... */
259 g_mutex_lock (&lock);
260 while (g_cond_wait_until (&local_cond, &lock, until))
261 ;
262 g_mutex_unlock (&lock);
263
264 /* Make sure it's after the until time */
265 g_assert_cmpint (until, <=, g_get_monotonic_time ());
266
267 /* Make sure it returns FALSE on timeout */
268 until = g_get_monotonic_time () + G_TIME_SPAN_SECOND / 50;
269 g_mutex_lock (&lock);
270 g_assert (g_cond_wait_until (&local_cond, &lock, until) == FALSE);
271 g_mutex_unlock (&lock);
272
273 g_mutex_clear (&lock);
274 g_cond_clear (&local_cond);
275 }
276
277 #ifdef __linux__
278
279 #include <pthread.h>
280 #include <signal.h>
281 #include <unistd.h>
282
283 static pthread_t main_thread;
284
285 static void *
286 mutex_holder (void *data)
287 {
288 GMutex *lock = data;
289
290 g_mutex_lock (lock);
291
292 /* Let the lock become contended */
293 g_usleep (G_TIME_SPAN_SECOND);
294
295 /* Interrupt the wait on the other thread */
296 pthread_kill (main_thread, SIGHUP);
297
298 /* If we don't sleep here, then the g_mutex_unlock() below will clear
299 * the mutex, causing the interrupted futex call in the other thread
300 * to return success (which is not what we want).
301 *
302 * The other thread needs to have time to wake up and see that the
303 * lock is still contended.
304 */
305 g_usleep (G_TIME_SPAN_SECOND / 10);
306
307 g_mutex_unlock (lock);
308
309 return NULL;
310 }
311
312 static void
313 signal_handler (int sig)
314 {
315 }
316
317 static void
318 test_wait_until_errno (void)
319 {
320 gboolean result;
321 GMutex lock;
322 GCond cond;
323 struct sigaction act = { };
324
325 /* important: no SA_RESTART (we want EINTR) */
326 act.sa_handler = signal_handler;
327
328 g_test_summary ("Check proper handling of errno in g_cond_wait_until with a contended mutex");
329 g_test_bug ("https://gitlab.gnome.org/GNOME/glib/merge_requests/957");
330
331 g_mutex_init (&lock);
332 g_cond_init (&cond);
333
334 main_thread = pthread_self ();
335 sigaction (SIGHUP, &act, NULL);
336
337 g_mutex_lock (&lock);
338
339 /* We create an annoying worker thread that will do two things:
340 *
341 * 1) hold the lock that we want to reacquire after returning from
342 * the condition variable wait
343 *
344 * 2) send us a signal to cause our wait on the contended lock to
345 * return EINTR, clobbering the errno return from the condition
346 * variable
347 */
348 g_thread_unref (g_thread_new ("mutex-holder", mutex_holder, &lock));
349
350 result = g_cond_wait_until (&cond, &lock,
351 g_get_monotonic_time () + G_TIME_SPAN_SECOND / 50);
352
353 /* Even after all that disruption, we should still successfully return
354 * 'timed out'.
355 */
356 g_assert_false (result);
357
358 g_mutex_unlock (&lock);
359
360 g_cond_clear (&cond);
361 g_mutex_clear (&lock);
362 }
363
364 #else
365 static void
366 test_wait_until_errno (void)
367 {
368 g_test_skip ("We only test this on Linux");
369 }
370 #endif
371
372 int
373 main (int argc, char *argv[])
374 {
375 g_test_init (&argc, &argv, NULL);
376
377 g_test_add_func ("/thread/cond1", test_cond1);
378 g_test_add_func ("/thread/cond2", test_cond2);
379 g_test_add_func ("/thread/cond/wait-until", test_wait_until);
380 g_test_add_func ("/thread/cond/wait-until/contended-and-interrupted", test_wait_until_errno);
381
382 return g_test_run ();
383 }