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