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
30 static GCond cond;
31 static GMutex mutex;
32 static gint next; /* locked by @mutex */
33
34 static void
push_value(gint value)35 push_value (gint value)
36 {
37 g_mutex_lock (&mutex);
38 while (next != 0)
39 g_cond_wait (&cond, &mutex);
40 next = value;
41 if (g_test_verbose ())
42 g_printerr ("Thread %p producing next value: %d\n", g_thread_self (), value);
43 if (value % 10 == 0)
44 g_cond_broadcast (&cond);
45 else
46 g_cond_signal (&cond);
47 g_mutex_unlock (&mutex);
48 }
49
50 static gint
pop_value(void)51 pop_value (void)
52 {
53 gint value;
54
55 g_mutex_lock (&mutex);
56 while (next == 0)
57 {
58 if (g_test_verbose ())
59 g_printerr ("Thread %p waiting for cond\n", g_thread_self ());
60 g_cond_wait (&cond, &mutex);
61 }
62 value = next;
63 next = 0;
64 g_cond_broadcast (&cond);
65 if (g_test_verbose ())
66 g_printerr ("Thread %p consuming value %d\n", g_thread_self (), value);
67 g_mutex_unlock (&mutex);
68
69 return value;
70 }
71
72 static gpointer
produce_values(gpointer data)73 produce_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 (i);
84 }
85
86 push_value (-1);
87 push_value (-1);
88
89 if (g_test_verbose ())
90 g_printerr ("Thread %p produced %d altogether\n", g_thread_self (), total);
91
92 return GINT_TO_POINTER (total);
93 }
94
95 static gpointer
consume_values(gpointer data)96 consume_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 ("Thread %p accumulated %d\n", g_thread_self (), accum);
112
113 return GINT_TO_POINTER (accum);
114 }
115
116 static GThread *producer, *consumer1, *consumer2;
117
118 static void
test_cond1(void)119 test_cond1 (void)
120 {
121 gint total, acc1, acc2;
122
123 producer = g_thread_create (produce_values, NULL, TRUE, NULL);
124 consumer1 = g_thread_create (consume_values, NULL, TRUE, NULL);
125 consumer2 = g_thread_create (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
134 typedef struct
135 {
136 GMutex mutex;
137 GCond cond;
138 gint limit;
139 gint count;
140 } Barrier;
141
142 static void
barrier_init(Barrier * barrier,gint limit)143 barrier_init (Barrier *barrier,
144 gint limit)
145 {
146 g_mutex_init (&barrier->mutex);
147 g_cond_init (&barrier->cond);
148 barrier->limit = limit;
149 barrier->count = limit;
150 }
151
152 static gint
barrier_wait(Barrier * barrier)153 barrier_wait (Barrier *barrier)
154 {
155 gint ret;
156
157 g_mutex_lock (&barrier->mutex);
158 barrier->count--;
159 if (barrier->count == 0)
160 {
161 ret = -1;
162 barrier->count = barrier->limit;
163 g_cond_broadcast (&barrier->cond);
164 }
165 else
166 {
167 ret = 0;
168 while (barrier->count != barrier->limit)
169 g_cond_wait (&barrier->cond, &barrier->mutex);
170 }
171 g_mutex_unlock (&barrier->mutex);
172
173 return ret;
174 }
175
176 static void
barrier_clear(Barrier * barrier)177 barrier_clear (Barrier *barrier)
178 {
179 g_mutex_clear (&barrier->mutex);
180 g_cond_clear (&barrier->cond);
181 }
182
183 static Barrier b;
184 static gint check;
185
186 static gpointer
cond2_func(gpointer data)187 cond2_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 ("thread %d starting, check %d\n", value, g_atomic_int_get (&check));
196
197 g_usleep (10000 * value);
198
199 g_atomic_int_inc (&check);
200
201 if (g_test_verbose ())
202 g_printerr ("thread %d reaching barrier, check %d\n", value, g_atomic_int_get (&check));
203
204 ret = barrier_wait (&b);
205
206 g_assert_cmpint (g_atomic_int_get (&check), ==, 10);
207
208 if (g_test_verbose ())
209 g_printerr ("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 */
217 static void
test_cond2(void)218 test_cond2 (void)
219 {
220 gint i;
221 GThread *threads[5];
222
223 g_atomic_int_set (&check, 0);
224
225 barrier_init (&b, 5);
226 for (i = 0; i < 5; i++)
227 threads[i] = g_thread_create (cond2_func, GINT_TO_POINTER (i), TRUE, NULL);
228
229 for (i = 0; i < 5; i++)
230 g_thread_join (threads[i]);
231
232 g_assert_cmpint (g_atomic_int_get (&check), ==, 10);
233
234 barrier_clear (&b);
235 }
236
237 static void
test_wait_until(void)238 test_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 (&lock);
252 g_cond_init (&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 (&lock);
258 while (g_cond_wait_until (&cond, &lock, until))
259 ;
260 g_mutex_unlock (&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 (&lock);
268 g_assert (g_cond_wait_until (&cond, &lock, until) == FALSE);
269 g_mutex_unlock (&lock);
270
271 g_mutex_clear (&lock);
272 g_cond_clear (&cond);
273 }
274
275 #ifdef __linux__
276
277 #include <pthread.h>
278 #include <signal.h>
279 #include <unistd.h>
280
281 static pthread_t main_thread;
282
283 static void *
mutex_holder(void * data)284 mutex_holder (void *data)
285 {
286 GMutex *lock = data;
287
288 g_mutex_lock (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 (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 (lock);
306
307 return NULL;
308 }
309
310 static void
signal_handler(int sig)311 signal_handler (int sig)
312 {
313 }
314
315 static void
test_wait_until_errno(void)316 test_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 ("Check proper handling of errno in g_cond_wait_until with a contended mutex");
327 g_test_bug ("https://gitlab.gnome.org/GNOME/glib/merge_requests/957");
328
329 g_mutex_init (&lock);
330 g_cond_init (&cond);
331
332 main_thread = pthread_self ();
333 sigaction (SIGHUP, &act, NULL);
334
335 g_mutex_lock (&lock);
336
337 /* We create an annoying worker thread that will do two things:
338 *
339 * 1) hold the lock that we want to reacquire after returning from
340 * the condition variable wait
341 *
342 * 2) send us a signal to cause our wait on the contended lock to
343 * return EINTR, clobbering the errno return from the condition
344 * variable
345 */
346 g_thread_unref (g_thread_new ("mutex-holder", mutex_holder, &lock));
347
348 result = g_cond_wait_until (&cond, &lock,
349 g_get_monotonic_time () + G_TIME_SPAN_SECOND / 50);
350
351 /* Even after all that disruption, we should still successfully return
352 * 'timed out'.
353 */
354 g_assert_false (result);
355
356 g_mutex_unlock (&lock);
357
358 g_cond_clear (&cond);
359 g_mutex_clear (&lock);
360 }
361
362 #else
363 static void
test_wait_until_errno(void)364 test_wait_until_errno (void)
365 {
366 g_test_skip ("We only test this on Linux");
367 }
368 #endif
369
370 int
main(int argc,char * argv[])371 main (int argc, char *argv[])
372 {
373 g_test_init (&argc, &argv, NULL);
374
375 g_test_add_func ("/thread/cond1", test_cond1);
376 g_test_add_func ("/thread/cond2", test_cond2);
377 g_test_add_func ("/thread/cond/wait-until", test_wait_until);
378 g_test_add_func ("/thread/cond/wait-until/contended-and-interrupted", test_wait_until_errno);
379
380 return g_test_run ();
381 }
382