044296f3a2ff719aa62ccd4cbeb3d9292e4fa992
[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_int *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_int),
100                    "Size mismatch!");
101     static_assert ((alignof (cond->value) % alignof (atomic_int)) == 0,
102                    "Alignment mismatch");
103     return (atomic_int *)&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 cond_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      * Here, we simply clear the low order bit, while cond_wait() sets it.
135      * It makes cond_wait() somewhat slower, but it should be free of bugs,
136      * leaves the other bits free for other uses (e.g. flags).
137      **/
138     atomic_fetch_and_explicit(vlc_cond_value(cond), -2, memory_order_relaxed);
139     /* We have to wake the futex even if the low order bit is cleared, as there
140      * could be more than one thread queued, not all of them already awoken. */
141     vlc_addr_signal(&cond->value);
142 }
143
144 void vlc_cond_broadcast(vlc_cond_t *cond)
145 {
146     atomic_fetch_and_explicit(vlc_cond_value(cond), -2, memory_order_relaxed);
147     vlc_addr_broadcast(&cond->value);
148 }
149
150 void vlc_cond_wait(vlc_cond_t *cond, vlc_mutex_t *mutex)
151 {
152     int value = atomic_fetch_or_explicit(vlc_cond_value(cond), 1,
153                                          memory_order_relaxed) | 1;
154
155     vlc_cancel_addr_prepare(&cond->value);
156     vlc_mutex_unlock(mutex);
157
158     vlc_addr_wait(&cond->value, value);
159
160     vlc_mutex_lock(mutex);
161     vlc_cancel_addr_finish(&cond->value);
162 }
163
164 static int vlc_cond_wait_delay(vlc_cond_t *cond, vlc_mutex_t *mutex,
165                                mtime_t delay)
166 {
167     int value = atomic_fetch_or_explicit(vlc_cond_value(cond), 1,
168                                          memory_order_relaxed) | 1;
169
170     vlc_cancel_addr_prepare(&cond->value);
171     vlc_mutex_unlock(mutex);
172
173     if (delay > 0)
174         value = vlc_addr_timedwait(&cond->value, value, delay);
175     else
176         value = 0;
177
178     vlc_mutex_lock(mutex);
179     vlc_cancel_addr_finish(&cond->value);
180
181     return value ? 0 : ETIMEDOUT;
182 }
183
184 int vlc_cond_timedwait(vlc_cond_t *cond, vlc_mutex_t *mutex, mtime_t deadline)
185 {
186     return vlc_cond_wait_delay(cond, mutex, deadline - mdate());
187 }
188
189 int vlc_cond_timedwait_daytime(vlc_cond_t *cond, vlc_mutex_t *mutex,
190                                time_t deadline)
191 {
192     struct timespec ts;
193
194     timespec_get(&ts, TIME_UTC);
195     deadline -= ts.tv_sec * CLOCK_FREQ;
196     deadline -= ts.tv_nsec / (1000000000 / CLOCK_FREQ);
197
198     return vlc_cond_wait_delay(cond, mutex, deadline);
199 }
200 #endif
201
202 #ifdef LIBVLC_NEED_RWLOCK
203 /*** Generic read/write locks ***/
204 #include <stdlib.h>
205 #include <limits.h>
206 /* NOTE:
207  * lock->state is a signed long integer:
208  *  - The sign bit is set when the lock is held for writing.
209  *  - The other bits code the number of times the lock is held for reading.
210  * Consequently:
211  *  - The value is negative if and only if the lock is held for writing.
212  *  - The value is zero if and only if the lock is not held at all.
213  */
214 #define READER_MASK LONG_MAX
215 #define WRITER_BIT  LONG_MIN
216
217 void vlc_rwlock_init (vlc_rwlock_t *lock)
218 {
219     vlc_mutex_init (&lock->mutex);
220     vlc_cond_init (&lock->wait);
221     lock->state = 0;
222 }
223
224 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
225 {
226     vlc_cond_destroy (&lock->wait);
227     vlc_mutex_destroy (&lock->mutex);
228 }
229
230 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
231 {
232     vlc_mutex_lock (&lock->mutex);
233     /* Recursive read-locking is allowed.
234      * Ensure that there is no active writer. */
235     while (lock->state < 0)
236     {
237         assert (lock->state == WRITER_BIT);
238         mutex_cleanup_push (&lock->mutex);
239         vlc_cond_wait (&lock->wait, &lock->mutex);
240         vlc_cleanup_pop ();
241     }
242     if (unlikely(lock->state >= READER_MASK))
243         abort (); /* An overflow is certainly a recursion bug. */
244     lock->state++;
245     vlc_mutex_unlock (&lock->mutex);
246 }
247
248 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
249 {
250     vlc_mutex_lock (&lock->mutex);
251     /* Wait until nobody owns the lock in any way. */
252     while (lock->state != 0)
253     {
254         mutex_cleanup_push (&lock->mutex);
255         vlc_cond_wait (&lock->wait, &lock->mutex);
256         vlc_cleanup_pop ();
257     }
258     lock->state = WRITER_BIT;
259     vlc_mutex_unlock (&lock->mutex);
260 }
261
262 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
263 {
264     vlc_mutex_lock (&lock->mutex);
265     if (lock->state < 0)
266     {   /* Write unlock */
267         assert (lock->state == WRITER_BIT);
268         /* Let reader and writer compete. OS scheduler decides who wins. */
269         lock->state = 0;
270         vlc_cond_broadcast (&lock->wait);
271     }
272     else
273     {   /* Read unlock */
274         assert (lock->state > 0);
275         /* If there are no readers left, wake up one pending writer. */
276         if (--lock->state == 0)
277             vlc_cond_signal (&lock->wait);
278     }
279     vlc_mutex_unlock (&lock->mutex);
280 }
281 #endif /* LIBVLC_NEED_RWLOCK */
282
283 #ifdef LIBVLC_NEED_SEMAPHORE
284 /*** Generic semaphores ***/
285 #include <limits.h>
286 #include <errno.h>
287
288 void vlc_sem_init (vlc_sem_t *sem, unsigned value)
289 {
290     vlc_mutex_init (&sem->lock);
291     vlc_cond_init (&sem->wait);
292     sem->value = value;
293 }
294
295 void vlc_sem_destroy (vlc_sem_t *sem)
296 {
297     vlc_cond_destroy (&sem->wait);
298     vlc_mutex_destroy (&sem->lock);
299 }
300
301 int vlc_sem_post (vlc_sem_t *sem)
302 {
303     int ret = 0;
304
305     vlc_mutex_lock (&sem->lock);
306     if (likely(sem->value != UINT_MAX))
307         sem->value++;
308     else
309         ret = EOVERFLOW;
310     vlc_mutex_unlock (&sem->lock);
311     vlc_cond_signal (&sem->wait);
312
313     return ret;
314 }
315
316 void vlc_sem_wait (vlc_sem_t *sem)
317 {
318     vlc_mutex_lock (&sem->lock);
319     mutex_cleanup_push (&sem->lock);
320     while (!sem->value)
321         vlc_cond_wait (&sem->wait, &sem->lock);
322     sem->value--;
323     vlc_cleanup_pop ();
324     vlc_mutex_unlock (&sem->lock);
325 }
326 #endif /* LIBVLC_NEED_SEMAPHORE */