Branch data Line data Source code
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 : 101 : push_value (gint value)
38 : : {
39 : 101 : g_mutex_lock (&mutex);
40 [ + + ]: 180 : while (next != 0)
41 : 79 : g_cond_wait (&cond, &mutex);
42 : 101 : next = value;
43 [ - + ]: 101 : if (g_test_verbose ())
44 : 0 : g_printerr ("Thread %p producing next value: %d\n", g_thread_self (), value);
45 [ + + ]: 101 : if (value % 10 == 0)
46 : 9 : g_cond_broadcast (&cond);
47 : : else
48 : 92 : g_cond_signal (&cond);
49 : 101 : g_mutex_unlock (&mutex);
50 : 101 : }
51 : :
52 : : static gint
53 : 101 : pop_value (void)
54 : : {
55 : : gint value;
56 : :
57 : 101 : g_mutex_lock (&mutex);
58 [ + + ]: 256 : while (next == 0)
59 : : {
60 [ - + ]: 155 : if (g_test_verbose ())
61 : 0 : g_printerr ("Thread %p waiting for cond\n", g_thread_self ());
62 : 155 : g_cond_wait (&cond, &mutex);
63 : : }
64 : 101 : value = next;
65 : 101 : next = 0;
66 : 101 : g_cond_broadcast (&cond);
67 [ - + ]: 101 : if (g_test_verbose ())
68 : 0 : g_printerr ("Thread %p consuming value %d\n", g_thread_self (), value);
69 : 101 : g_mutex_unlock (&mutex);
70 : :
71 : 101 : return value;
72 : : }
73 : :
74 : : static gpointer
75 : 1 : produce_values (gpointer data)
76 : : {
77 : : gint total;
78 : : gint i;
79 : :
80 : 1 : total = 0;
81 : :
82 [ + + ]: 100 : for (i = 1; i < 100; i++)
83 : : {
84 : 99 : total += i;
85 : 99 : push_value (i);
86 : : }
87 : :
88 : 1 : push_value (-1);
89 : 1 : push_value (-1);
90 : :
91 [ - + ]: 1 : if (g_test_verbose ())
92 : 0 : g_printerr ("Thread %p produced %d altogether\n", g_thread_self (), total);
93 : :
94 : 1 : return GINT_TO_POINTER (total);
95 : : }
96 : :
97 : : static gpointer
98 : 2 : consume_values (gpointer data)
99 : : {
100 : 2 : gint accum = 0;
101 : : gint value;
102 : :
103 : : while (TRUE)
104 : : {
105 : 101 : value = pop_value ();
106 [ + + ]: 101 : if (value == -1)
107 : 2 : break;
108 : :
109 : 99 : accum += value;
110 : : }
111 : :
112 [ - + ]: 2 : if (g_test_verbose ())
113 : 0 : g_printerr ("Thread %p accumulated %d\n", g_thread_self (), accum);
114 : :
115 : 2 : return GINT_TO_POINTER (accum);
116 : : }
117 : :
118 : : static GThread *producer, *consumer1, *consumer2;
119 : :
120 : : static void
121 : 1 : test_cond1 (void)
122 : : {
123 : : gint total, acc1, acc2;
124 : :
125 : 1 : producer = g_thread_create (produce_values, NULL, TRUE, NULL);
126 : 1 : consumer1 = g_thread_create (consume_values, NULL, TRUE, NULL);
127 : 1 : consumer2 = g_thread_create (consume_values, NULL, TRUE, NULL);
128 : :
129 : 1 : total = GPOINTER_TO_INT (g_thread_join (producer));
130 : 1 : acc1 = GPOINTER_TO_INT (g_thread_join (consumer1));
131 : 1 : acc2 = GPOINTER_TO_INT (g_thread_join (consumer2));
132 : :
133 : 1 : g_assert_cmpint (total, ==, acc1 + acc2);
134 : 1 : }
135 : :
136 : : typedef struct
137 : : {
138 : : GMutex mutex;
139 : : GCond cond;
140 : : gint limit;
141 : : gint count;
142 : : } Barrier;
143 : :
144 : : static void
145 : 1 : barrier_init (Barrier *barrier,
146 : : gint limit)
147 : : {
148 : 1 : g_mutex_init (&barrier->mutex);
149 : 1 : g_cond_init (&barrier->cond);
150 : 1 : barrier->limit = limit;
151 : 1 : barrier->count = limit;
152 : 1 : }
153 : :
154 : : static gint
155 : 5 : barrier_wait (Barrier *barrier)
156 : : {
157 : : gint ret;
158 : :
159 : 5 : g_mutex_lock (&barrier->mutex);
160 : 5 : barrier->count--;
161 [ + + ]: 5 : if (barrier->count == 0)
162 : : {
163 : 1 : ret = -1;
164 : 1 : barrier->count = barrier->limit;
165 : 1 : g_cond_broadcast (&barrier->cond);
166 : : }
167 : : else
168 : : {
169 : 4 : ret = 0;
170 [ + + ]: 8 : while (barrier->count != barrier->limit)
171 : 4 : g_cond_wait (&barrier->cond, &barrier->mutex);
172 : : }
173 : 5 : g_mutex_unlock (&barrier->mutex);
174 : :
175 : 5 : return ret;
176 : : }
177 : :
178 : : static void
179 : 1 : barrier_clear (Barrier *barrier)
180 : : {
181 : 1 : g_mutex_clear (&barrier->mutex);
182 : 1 : g_cond_clear (&barrier->cond);
183 : 1 : }
184 : :
185 : : static Barrier b;
186 : : static gint check;
187 : :
188 : : static gpointer
189 : 5 : cond2_func (gpointer data)
190 : : {
191 : 5 : gint value = GPOINTER_TO_INT (data);
192 : : gint ret;
193 : :
194 : 5 : g_atomic_int_inc (&check);
195 : :
196 [ - + ]: 5 : if (g_test_verbose ())
197 : 0 : g_printerr ("thread %d starting, check %d\n", value, g_atomic_int_get (&check));
198 : :
199 : 5 : g_usleep (10000 * value);
200 : :
201 : 5 : g_atomic_int_inc (&check);
202 : :
203 [ - + ]: 5 : if (g_test_verbose ())
204 : 0 : g_printerr ("thread %d reaching barrier, check %d\n", value, g_atomic_int_get (&check));
205 : :
206 : 5 : ret = barrier_wait (&b);
207 : :
208 : 5 : g_assert_cmpint (g_atomic_int_get (&check), ==, 10);
209 : :
210 [ - + ]: 5 : if (g_test_verbose ())
211 : 0 : g_printerr ("thread %d leaving barrier (%d), check %d\n", value, ret, g_atomic_int_get (&check));
212 : :
213 : 5 : return NULL;
214 : : }
215 : :
216 : : /* this test demonstrates how to use a condition variable
217 : : * to implement a barrier
218 : : */
219 : : static void
220 : 1 : test_cond2 (void)
221 : : {
222 : : gint i;
223 : : GThread *threads[5];
224 : :
225 : 1 : g_atomic_int_set (&check, 0);
226 : :
227 : 1 : barrier_init (&b, 5);
228 [ + + ]: 6 : for (i = 0; i < 5; i++)
229 : 5 : threads[i] = g_thread_create (cond2_func, GINT_TO_POINTER (i), TRUE, NULL);
230 : :
231 [ + + ]: 6 : for (i = 0; i < 5; i++)
232 : 5 : g_thread_join (threads[i]);
233 : :
234 : 1 : g_assert_cmpint (g_atomic_int_get (&check), ==, 10);
235 : :
236 : 1 : barrier_clear (&b);
237 : 1 : }
238 : :
239 : : static void
240 : 1 : 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 : 1 : g_mutex_init (&lock);
254 : 1 : g_cond_init (&local_cond);
255 : :
256 : 1 : until = g_get_monotonic_time () + G_TIME_SPAN_SECOND;
257 : :
258 : : /* Could still have spurious wakeups, so we must loop... */
259 : 1 : g_mutex_lock (&lock);
260 [ - + ]: 1 : while (g_cond_wait_until (&local_cond, &lock, until))
261 : : ;
262 : 1 : g_mutex_unlock (&lock);
263 : :
264 : : /* Make sure it's after the until time */
265 : 1 : g_assert_cmpint (until, <=, g_get_monotonic_time ());
266 : :
267 : : /* Make sure it returns FALSE on timeout */
268 : 1 : until = g_get_monotonic_time () + G_TIME_SPAN_SECOND / 50;
269 : 1 : g_mutex_lock (&lock);
270 : 1 : g_assert (g_cond_wait_until (&local_cond, &lock, until) == FALSE);
271 : 1 : g_mutex_unlock (&lock);
272 : :
273 : 1 : g_mutex_clear (&lock);
274 : 1 : g_cond_clear (&local_cond);
275 : 1 : }
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 : 1 : mutex_holder (void *data)
287 : : {
288 : 1 : GMutex *lock = data;
289 : :
290 : 1 : g_mutex_lock (lock);
291 : :
292 : : /* Let the lock become contended */
293 : 1 : g_usleep (G_TIME_SPAN_SECOND);
294 : :
295 : : /* Interrupt the wait on the other thread */
296 : 1 : 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 : 1 : g_usleep (G_TIME_SPAN_SECOND / 10);
306 : :
307 : 1 : g_mutex_unlock (lock);
308 : :
309 : 1 : return NULL;
310 : : }
311 : :
312 : : static void
313 : 1 : signal_handler (int sig)
314 : : {
315 : 1 : }
316 : :
317 : : static void
318 : 1 : test_wait_until_errno (void)
319 : : {
320 : : gboolean result;
321 : : GMutex lock;
322 : : GCond cond;
323 : 1 : struct sigaction act = { };
324 : :
325 : : /* important: no SA_RESTART (we want EINTR) */
326 : 1 : act.sa_handler = signal_handler;
327 : :
328 : 1 : g_test_summary ("Check proper handling of errno in g_cond_wait_until with a contended mutex");
329 : 1 : g_test_bug ("https://gitlab.gnome.org/GNOME/glib/merge_requests/957");
330 : :
331 : 1 : g_mutex_init (&lock);
332 : 1 : g_cond_init (&cond);
333 : :
334 : 1 : main_thread = pthread_self ();
335 : 1 : sigaction (SIGHUP, &act, NULL);
336 : :
337 : 1 : 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 : 1 : g_thread_unref (g_thread_new ("mutex-holder", mutex_holder, &lock));
349 : :
350 : 1 : result = g_cond_wait_until (&cond, &lock,
351 : 1 : 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 : 1 : g_assert_false (result);
357 : :
358 : 1 : g_mutex_unlock (&lock);
359 : :
360 : 1 : g_cond_clear (&cond);
361 : 1 : g_mutex_clear (&lock);
362 : 1 : }
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 : 1 : main (int argc, char *argv[])
374 : : {
375 : 1 : g_test_init (&argc, &argv, NULL);
376 : :
377 : 1 : g_test_add_func ("/thread/cond1", test_cond1);
378 : 1 : g_test_add_func ("/thread/cond2", test_cond2);
379 : 1 : g_test_add_func ("/thread/cond/wait-until", test_wait_until);
380 : 1 : g_test_add_func ("/thread/cond/wait-until/contended-and-interrupted", test_wait_until_errno);
381 : :
382 : 1 : return g_test_run ();
383 : : }
|