threads: fix race in vlc_cond_wait()
[vlc.git] / src / misc / threads.c
1 /*****************************************************************************
2  * threads.c: LibVLC generic thread support
3  *****************************************************************************
4  * Copyright (C) 2009-2016 RĂ©mi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program 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. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <assert.h>
26 #include <errno.h>
27
28 #include <vlc_common.h>
29
30 /*** Global locks ***/
31
32 void vlc_global_mutex (unsigned n, bool acquire)
33 {
34     static vlc_mutex_t locks[] = {
35         VLC_STATIC_MUTEX,
36         VLC_STATIC_MUTEX,
37         VLC_STATIC_MUTEX,
38         VLC_STATIC_MUTEX,
39         VLC_STATIC_MUTEX,
40     };
41     static_assert (VLC_MAX_MUTEX == (sizeof (locks) / sizeof (locks[0])),
42                    "Wrong number of global mutexes");
43     assert (n < (sizeof (locks) / sizeof (locks[0])));
44
45     vlc_mutex_t *lock = locks + n;
46     if (acquire)
47         vlc_mutex_lock (lock);
48     else
49         vlc_mutex_unlock (lock);
50 }
51
52 #if defined(LIBVLC_NEED_SLEEP) || defined(LIBVLC_NEED_CONDVAR)
53 #include <vlc_atomic.h>
54
55 static void vlc_cancel_addr_prepare(void *addr)
56 {
57     /* Let thread subsystem on address to broadcast for cancellation */
58     vlc_cancel_addr_set(addr);
59     vlc_cleanup_push(vlc_cancel_addr_clear, addr);
60     /* Check if cancellation was pending before vlc_cancel_addr_set() */
61     vlc_testcancel();
62     vlc_cleanup_pop();
63 }
64
65 static void vlc_cancel_addr_finish(void *addr)
66 {
67     vlc_cancel_addr_clear(addr);
68     /* Act on cancellation as potential wake-up source */
69     vlc_testcancel();
70 }
71 #endif
72
73 #ifdef LIBVLC_NEED_SLEEP
74 void (mwait)(mtime_t deadline)
75 {
76     mtime_t delay;
77     atomic_int value = ATOMIC_VAR_INIT(0);
78
79     vlc_cancel_addr_prepare(&value);
80
81     while ((delay = (deadline - mdate())) > 0)
82         vlc_addr_timedwait(&value, 0, delay);
83
84     vlc_cancel_addr_finish(&value);
85 }
86
87 void (msleep)(mtime_t delay)
88 {
89     mwait(mdate() + delay);
90 }
91 #endif
92
93 #ifdef LIBVLC_NEED_CONDVAR
94 #include <stdalign.h>
95
96 static inline atomic_uint *vlc_cond_value(vlc_cond_t *cond)
97 {
98     /* XXX: ugly but avoids including vlc_atomic.h in vlc_threads.h */
99     static_assert (sizeof (cond->value) <= sizeof (atomic_uint),
100                    "Size mismatch!");
101     static_assert ((alignof (cond->value) % alignof (atomic_uint)) == 0,
102                    "Alignment mismatch");
103     return (atomic_uint *)&cond->value;
104 }
105
106 void vlc_cond_init(vlc_cond_t *cond)
107 {
108     /* Initial value is irrelevant but set it for happy debuggers */
109     atomic_init(vlc_cond_value(cond), 0);
110 }
111
112 void vlc_cond_init_daytime(vlc_cond_t *cond)
113 {
114     vlc_cond_init(cond);
115 }
116
117 void vlc_cond_destroy(vlc_cond_t *cond)
118 {
119     /* Tempting sanity check but actually incorrect:
120     assert((atomic_load_explicit(vlc_cond_value(cond),
121                                  memory_order_relaxed) & 1) == 0);
122      * Due to timeouts and spurious wake-ups, the futex value can look like
123      * there are waiters, even though there are none. */
124     (void) cond;
125 }
126
127 void vlc_cond_signal(vlc_cond_t *cond)
128 {
129     /* Probably the best documented approach is that of Bionic: increment
130      * the futex here, and simply load the value in cnd_wait(). This has a bug
131      * as unlikely as well-known: signals get lost if the futex is incremented
132      * an exact multiple of 2^(CHAR_BIT * sizeof (int)) times.
133      *
134      * A different presumably bug-free solution is used here:
135      * - cnd_signal() sets the futex to the equal-or-next odd value, while
136      * - cnd_wait() sets the futex to the equal-or-next even value.
137      **/
138     atomic_fetch_or_explicit(vlc_cond_value(cond), 1, memory_order_relaxed);
139     vlc_addr_signal(&cond->value);
140 }
141
142 void vlc_cond_broadcast(vlc_cond_t *cond)
143 {
144     atomic_fetch_or_explicit(vlc_cond_value(cond), 1, memory_order_relaxed);
145     vlc_addr_broadcast(&cond->value);
146 }
147
148 void vlc_cond_wait(vlc_cond_t *cond, vlc_mutex_t *mutex)
149 {
150     unsigned value = atomic_load_explicit(vlc_cond_value(cond),
151                                      memory_order_relaxed);
152     while (value & 1)
153     {
154         if (atomic_compare_exchange_weak_explicit(vlc_cond_value(cond), &value,
155                                                   value + 1,
156                                                   memory_order_relaxed,
157                                                   memory_order_relaxed))
158             value++;
159     }
160
161     vlc_cancel_addr_prepare(&cond->value);
162     vlc_mutex_unlock(mutex);
163
164     vlc_addr_wait(&cond->value, value);
165
166     vlc_mutex_lock(mutex);
167     vlc_cancel_addr_finish(&cond->value);
168 }
169
170 static int vlc_cond_wait_delay(vlc_cond_t *cond, vlc_mutex_t *mutex,
171                                mtime_t delay)
172 {
173     unsigned value = atomic_load_explicit(vlc_cond_value(cond),
174                                           memory_order_relaxed);
175     while (value & 1)
176     {
177         if (atomic_compare_exchange_weak_explicit(vlc_cond_value(cond), &value,
178                                                   value + 1,
179                                                   memory_order_relaxed,
180                                                   memory_order_relaxed))
181             value++;
182     }
183
184     vlc_cancel_addr_prepare(&cond->value);
185     vlc_mutex_unlock(mutex);
186
187     if (delay > 0)
188         value = vlc_addr_timedwait(&cond->value, value, delay);
189     else
190         value = 0;
191
192     vlc_mutex_lock(mutex);
193     vlc_cancel_addr_finish(&cond->value);
194
195     return value ? 0 : ETIMEDOUT;
196 }
197
198 int vlc_cond_timedwait(vlc_cond_t *cond, vlc_mutex_t *mutex, mtime_t deadline)
199 {
200     return vlc_cond_wait_delay(cond, mutex, deadline - mdate());
201 }
202
203 int vlc_cond_timedwait_daytime(vlc_cond_t *cond, vlc_mutex_t *mutex,
204                                time_t deadline)
205 {
206     struct timespec ts;
207
208     timespec_get(&ts, TIME_UTC);
209     deadline -= ts.tv_sec * CLOCK_FREQ;
210     deadline -= ts.tv_nsec / (1000000000 / CLOCK_FREQ);
211
212     return vlc_cond_wait_delay(cond, mutex, deadline);
213 }
214 #endif
215
216 #ifdef LIBVLC_NEED_RWLOCK
217 /*** Generic read/write locks ***/
218 #include <stdlib.h>
219 #include <limits.h>
220 /* NOTE:
221  * lock->state is a signed long integer:
222  *  - The sign bit is set when the lock is held for writing.
223  *  - The other bits code the number of times the lock is held for reading.
224  * Consequently:
225  *  - The value is negative if and only if the lock is held for writing.
226  *  - The value is zero if and only if the lock is not held at all.
227  */
228 #define READER_MASK LONG_MAX
229 #define WRITER_BIT  LONG_MIN
230
231 void vlc_rwlock_init (vlc_rwlock_t *lock)
232 {
233     vlc_mutex_init (&lock->mutex);
234     vlc_cond_init (&lock->wait);
235     lock->state = 0;
236 }
237
238 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
239 {
240     vlc_cond_destroy (&lock->wait);
241     vlc_mutex_destroy (&lock->mutex);
242 }
243
244 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
245 {
246     vlc_mutex_lock (&lock->mutex);
247     /* Recursive read-locking is allowed.
248      * Ensure that there is no active writer. */
249     while (lock->state < 0)
250     {
251         assert (lock->state == WRITER_BIT);
252         mutex_cleanup_push (&lock->mutex);
253         vlc_cond_wait (&lock->wait, &lock->mutex);
254         vlc_cleanup_pop ();
255     }
256     if (unlikely(lock->state >= READER_MASK))
257         abort (); /* An overflow is certainly a recursion bug. */
258     lock->state++;
259     vlc_mutex_unlock (&lock->mutex);
260 }
261
262 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
263 {
264     vlc_mutex_lock (&lock->mutex);
265     /* Wait until nobody owns the lock in any way. */
266     while (lock->state != 0)
267     {
268         mutex_cleanup_push (&lock->mutex);
269         vlc_cond_wait (&lock->wait, &lock->mutex);
270         vlc_cleanup_pop ();
271     }
272     lock->state = WRITER_BIT;
273     vlc_mutex_unlock (&lock->mutex);
274 }
275
276 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
277 {
278     vlc_mutex_lock (&lock->mutex);
279     if (lock->state < 0)
280     {   /* Write unlock */
281         assert (lock->state == WRITER_BIT);
282         /* Let reader and writer compete. OS scheduler decides who wins. */
283         lock->state = 0;
284         vlc_cond_broadcast (&lock->wait);
285     }
286     else
287     {   /* Read unlock */
288         assert (lock->state > 0);
289         /* If there are no readers left, wake up one pending writer. */
290         if (--lock->state == 0)
291             vlc_cond_signal (&lock->wait);
292     }
293     vlc_mutex_unlock (&lock->mutex);
294 }
295 #endif /* LIBVLC_NEED_RWLOCK */
296
297 #ifdef LIBVLC_NEED_SEMAPHORE
298 /*** Generic semaphores ***/
299 #include <limits.h>
300 #include <errno.h>
301
302 void vlc_sem_init (vlc_sem_t *sem, unsigned value)
303 {
304     vlc_mutex_init (&sem->lock);
305     vlc_cond_init (&sem->wait);
306     sem->value = value;
307 }
308
309 void vlc_sem_destroy (vlc_sem_t *sem)
310 {
311     vlc_cond_destroy (&sem->wait);
312     vlc_mutex_destroy (&sem->lock);
313 }
314
315 int vlc_sem_post (vlc_sem_t *sem)
316 {
317     int ret = 0;
318
319     vlc_mutex_lock (&sem->lock);
320     if (likely(sem->value != UINT_MAX))
321         sem->value++;
322     else
323         ret = EOVERFLOW;
324     vlc_mutex_unlock (&sem->lock);
325     vlc_cond_signal (&sem->wait);
326
327     return ret;
328 }
329
330 void vlc_sem_wait (vlc_sem_t *sem)
331 {
332     vlc_mutex_lock (&sem->lock);
333     mutex_cleanup_push (&sem->lock);
334     while (!sem->value)
335         vlc_cond_wait (&sem->wait, &sem->lock);
336     sem->value--;
337     vlc_cleanup_pop ();
338     vlc_mutex_unlock (&sem->lock);
339 }
340 #endif /* LIBVLC_NEED_SEMAPHORE */