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

source code of gtk/subprojects/glib/glib/tests/cond.c