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