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 |
35 | push_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 | |
50 | static gint |
51 | pop_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 | |
72 | static gpointer |
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 (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 | |
95 | static gpointer |
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 (format: "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 |
119 | test_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 | |
134 | typedef struct |
135 | { |
136 | GMutex mutex; |
137 | GCond cond; |
138 | gint limit; |
139 | gint count; |
140 | } Barrier; |
141 | |
142 | static void |
143 | barrier_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 | |
152 | static gint |
153 | barrier_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 | |
176 | static void |
177 | barrier_clear (Barrier *barrier) |
178 | { |
179 | g_mutex_clear (mutex: &barrier->mutex); |
180 | g_cond_clear (cond: &barrier->cond); |
181 | } |
182 | |
183 | static Barrier b; |
184 | static gint check; |
185 | |
186 | static gpointer |
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 (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 | */ |
217 | static 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 (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 | |
237 | static 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 (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 | |
281 | static pthread_t main_thread; |
282 | |
283 | static void * |
284 | mutex_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 | |
310 | static void |
311 | signal_handler (int sig) |
312 | { |
313 | } |
314 | |
315 | static 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 (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 |
364 | static void |
365 | test_wait_until_errno (void) |
366 | { |
367 | g_test_skip ("We only test this on Linux" ); |
368 | } |
369 | #endif |
370 | |
371 | int |
372 | main (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 | |