]> git.sesse.net Git - vlc/blob - src/misc/w32thread.c
Read/write locks
[vlc] / src / misc / w32thread.c
1 /*****************************************************************************
2  * w32thread.c : Win32 back-end for LibVLC
3  *****************************************************************************
4  * Copyright (C) 1999-2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Jean-Marc Dressler <polux@via.ecp.fr>
8  *          Samuel Hocevar <sam@zoy.org>
9  *          Gildas Bazin <gbazin@netcourrier.com>
10  *          Clément Sténac
11  *          Rémi Denis-Courmont
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33
34 #include "libvlc.h"
35 #include <stdarg.h>
36 #include <assert.h>
37 #include <limits.h>
38
39 static vlc_threadvar_t cancel_key;
40
41 /**
42  * Per-thread cancellation data
43  */
44 typedef struct vlc_cancel_t
45 {
46     vlc_cleanup_t *cleaners;
47 #ifdef UNDER_CE
48     HANDLE         cancel_event;
49 #endif
50     bool           killable;
51     bool           killed;
52 } vlc_cancel_t;
53
54 #ifndef UNDER_CE
55 # define VLC_CANCEL_INIT { NULL, true, false }
56 #else
57 # define VLC_CANCEL_INIT { NULL, NULL, true, false }
58 #endif
59
60 #ifdef UNDER_CE
61 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
62
63 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
64                                   DWORD delay)
65 {
66     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
67     if (nfo == NULL)
68     {
69         /* Main thread - cannot be cancelled anyway */
70         return WaitForMultipleObjects (count, handles, FALSE, delay);
71     }
72     HANDLE new_handles[count + 1];
73     memcpy(new_handles, handles, count * sizeof(HANDLE));
74     new_handles[count] = nfo->cancel_event;
75     DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
76                                            delay);
77     if (result == WAIT_OBJECT_0 + count)
78     {
79         vlc_cancel_self (NULL);
80         return WAIT_IO_COMPLETION;
81     }
82     else
83     {
84         return result;
85     }
86 }
87
88 DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
89 {
90     if (bAlertable)
91     {
92         DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
93         return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
94     }
95     else
96     {
97         Sleep(dwMilliseconds);
98         return 0;
99     }
100 }
101
102 DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
103                              BOOL bAlertable)
104 {
105     if (bAlertable)
106     {
107         /* The MSDN documentation specifies different return codes,
108          * but in practice they are the same. We just check that it
109          * remains so. */
110 #if WAIT_ABANDONED != WAIT_ABANDONED_0
111 # error Windows headers changed, code needs to be rewritten!
112 #endif
113         return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
114     }
115     else
116     {
117         return WaitForSingleObject (hHandle, dwMilliseconds);
118     }
119 }
120
121 DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
122                                 BOOL bWaitAll, DWORD dwMilliseconds,
123                                 BOOL bAlertable)
124 {
125     if (bAlertable)
126     {
127         /* We do not support the bWaitAll case */
128         assert (! bWaitAll);
129         return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
130     }
131     else
132     {
133         return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
134                                        dwMilliseconds);
135     }
136 }
137 #endif
138
139 static vlc_mutex_t super_mutex;
140
141 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
142 {
143     (void) hinstDll;
144     (void) lpvReserved;
145
146     switch (fdwReason)
147     {
148         case DLL_PROCESS_ATTACH:
149             vlc_mutex_init (&super_mutex);
150             vlc_threadvar_create (&cancel_key, free);
151             break;
152
153         case DLL_PROCESS_DETACH:
154             vlc_threadvar_delete( &cancel_key );
155             vlc_mutex_destroy (&super_mutex);
156             break;
157     }
158     return TRUE;
159 }
160
161 /*** Mutexes ***/
162 void vlc_mutex_init( vlc_mutex_t *p_mutex )
163 {
164     /* This creates a recursive mutex. This is OK as fast mutexes have
165      * no defined behavior in case of recursive locking. */
166     InitializeCriticalSection (&p_mutex->mutex);
167     p_mutex->initialized = 1;
168 }
169
170 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
171 {
172     InitializeCriticalSection( &p_mutex->mutex );
173     p_mutex->initialized = 1;
174 }
175
176
177 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
178 {
179     assert (InterlockedExchange (&p_mutex->initialized, -1) == 1);
180     DeleteCriticalSection (&p_mutex->mutex);
181 }
182
183 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
184 {
185     if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
186     { /* ^^ We could also lock super_mutex all the time... sluggish */
187         assert (p_mutex != &super_mutex); /* this one cannot be static */
188
189         vlc_mutex_lock (&super_mutex);
190         if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
191             vlc_mutex_init (p_mutex);
192         /* FIXME: destroy the mutex some time... */
193         vlc_mutex_unlock (&super_mutex);
194     }
195     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
196     EnterCriticalSection (&p_mutex->mutex);
197 }
198
199 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
200 {
201     if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
202     { /* ^^ We could also lock super_mutex all the time... sluggish */
203         assert (p_mutex != &super_mutex); /* this one cannot be static */
204
205         vlc_mutex_lock (&super_mutex);
206         if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
207             vlc_mutex_init (p_mutex);
208         /* FIXME: destroy the mutex some time... */
209         vlc_mutex_unlock (&super_mutex);
210     }
211     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
212     return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
213 }
214
215 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
216 {
217     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
218     LeaveCriticalSection (&p_mutex->mutex);
219 }
220
221 /*** Condition variables ***/
222 void vlc_cond_init( vlc_cond_t *p_condvar )
223 {
224     /* Create a manual-reset event (manual reset is needed for broadcast). */
225     *p_condvar = CreateEvent (NULL, TRUE, FALSE, NULL);
226     if (!*p_condvar)
227         abort();
228 }
229
230 void vlc_cond_destroy (vlc_cond_t *p_condvar)
231 {
232     CloseHandle (*p_condvar);
233 }
234
235 void vlc_cond_signal (vlc_cond_t *p_condvar)
236 {
237     /* NOTE: This will cause a broadcast, that is wrong.
238      * This will also wake up the next waiting thread if no threads are yet
239      * waiting, which is also wrong. However both of these issues are allowed
240      * by the provision for spurious wakeups. Better have too many wakeups
241      * than too few (= deadlocks). */
242     SetEvent (*p_condvar);
243 }
244
245 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
246 {
247     SetEvent (*p_condvar);
248 }
249
250 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
251 {
252     DWORD result;
253
254     do
255     {
256         vlc_testcancel ();
257         LeaveCriticalSection (&p_mutex->mutex);
258         result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE);
259         EnterCriticalSection (&p_mutex->mutex);
260     }
261     while (result == WAIT_IO_COMPLETION);
262
263     assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
264     assert (result != WAIT_FAILED);
265     ResetEvent (*p_condvar);
266 }
267
268 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
269                         mtime_t deadline)
270 {
271     DWORD result;
272
273     do
274     {
275         vlc_testcancel ();
276
277         mtime_t total = (deadline - mdate ())/1000;
278         if( total < 0 )
279             total = 0;
280
281         DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
282         LeaveCriticalSection (&p_mutex->mutex);
283         result = WaitForSingleObjectEx (*p_condvar, delay, TRUE);
284         EnterCriticalSection (&p_mutex->mutex);
285     }
286     while (result == WAIT_IO_COMPLETION);
287
288     assert (result != WAIT_ABANDONED);
289     assert (result != WAIT_FAILED);
290     ResetEvent (*p_condvar);
291
292     return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
293 }
294
295 /*** Read/write locks */
296 /* SRW (Slim Read Write) locks are available in Vista+ only */
297 void vlc_rwlock_init (vlc_rwlock_t *lock)
298 {
299     vlc_mutex_init (&lock->mutex);
300     vlc_cond_init (&lock->read_wait);
301     vlc_cond_init (&lock->write_wait);
302     lock->readers = 0; /* active readers */
303     lock->writers = 0; /* waiting or active writers */
304     lock->writer = 0; /* ID of active writer */
305 }
306
307 /**
308  * Destroys an initialized unused read/write lock.
309  */
310 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
311 {
312     vlc_cond_destroy (&lock->read_wait);
313     vlc_cond_destroy (&lock->write_wait);
314     vlc_mutex_destroy (&lock->mutex);
315 }
316
317 /**
318  * Acquires a read/write lock for reading. Recursion is allowed.
319  */
320 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
321 {
322     vlc_mutex_lock (&lock->mutex);
323     while (lock->writers > 0) /* Favor writers to avoid starving */
324         vlc_cond_wait (&lock->read_wait, &lock->mutex);
325     if (lock->readers == ULONG_MAX)
326         abort ();
327     lock->readers++;
328     vlc_mutex_unlock (&lock->mutex);
329 }
330
331 /**
332  * Acquires a read/write lock for writing. Recursion is not allowed.
333  */
334 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
335 {
336     vlc_mutex_lock (&lock->mutex);
337     if (lock->writers == ULONG_MAX)
338         abort ();
339     lock->writers++;
340     while ((lock->readers > 0) || (lock->writer != 0))
341         vlc_cond_wait (&lock->write_wait, &lock->mutex);
342     lock->writer = GetCurrentThreadId ();
343     vlc_mutex_unlock (&lock->mutex);
344 }
345
346 /**
347  * Releases a read/write lock.
348  */
349 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
350 {
351     vlc_mutex_lock (&lock->mutex);
352     if (lock->readers > 0)
353         lock->readers--; /* Read unlock */
354     else
355     {
356         lock->writer = 0; /* Write unlock */
357         assert (lock->writers > 0);
358         lock->writers--;
359     }
360
361     if (lock->writers > 0)
362     {
363         if (lock->readers == 0)
364             vlc_cond_signal (&lock->write_wait);
365     }
366     else
367         vlc_cond_broadcast (&lock->read_wait);
368     vlc_mutex_unlock (&lock->mutex);
369 }
370
371 /*** Thread-specific variables (TLS) ***/
372 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
373 {
374 #warning FIXME: use destr() callback and stop leaking!
375     *p_tls = TlsAlloc();
376     return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
377 }
378
379 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
380 {
381     TlsFree (*p_tls);
382 }
383
384 /**
385  * Sets a thread-local variable.
386  * @param key thread-local variable key (created with vlc_threadvar_create())
387  * @param value new value for the variable for the calling thread
388  * @return 0 on success, a system error code otherwise.
389  */
390 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
391 {
392     return TlsSetValue (key, value) ? ENOMEM : 0;
393 }
394
395 /**
396  * Gets the value of a thread-local variable for the calling thread.
397  * This function cannot fail.
398  * @return the value associated with the given variable for the calling
399  * or NULL if there is no value.
400  */
401 void *vlc_threadvar_get (vlc_threadvar_t key)
402 {
403     return TlsGetValue (key);
404 }
405
406
407 /*** Threads ***/
408 static unsigned __stdcall vlc_entry (void *data)
409 {
410     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
411     vlc_thread_t self = data;
412 #ifdef UNDER_CE
413     cancel_data.cancel_event = self->cancel_event;
414 #endif
415
416     vlc_threadvar_set (cancel_key, &cancel_data);
417     self->data = self->entry (self->data);
418     return 0;
419 }
420
421 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
422                int priority)
423 {
424     /* When using the MSVCRT C library you have to use the _beginthreadex
425      * function instead of CreateThread, otherwise you'll end up with
426      * memory leaks and the signal functions not working (see Microsoft
427      * Knowledge Base, article 104641) */
428     HANDLE hThread;
429     vlc_thread_t th = malloc (sizeof (*th));
430
431     if (th == NULL)
432         return ENOMEM;
433
434     th->data = data;
435     th->entry = entry;
436 #if defined( UNDER_CE )
437     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
438     if (th->cancel_event == NULL)
439     {
440         free(th);
441         return errno;
442     }
443     hThread = CreateThread (NULL, 128*1024, vlc_entry, th, CREATE_SUSPENDED, NULL);
444 #else
445     hThread = (HANDLE)(uintptr_t)
446         _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
447 #endif
448
449     if (hThread)
450     {
451 #ifndef UNDER_CE
452         /* Thread closes the handle when exiting, duplicate it here
453          * to be on the safe side when joining. */
454         if (!DuplicateHandle (GetCurrentProcess (), hThread,
455                               GetCurrentProcess (), &th->handle, 0, FALSE,
456                               DUPLICATE_SAME_ACCESS))
457         {
458             CloseHandle (hThread);
459             free (th);
460             return ENOMEM;
461         }
462 #else
463         th->handle = hThread;
464 #endif
465
466         ResumeThread (hThread);
467         if (priority)
468             SetThreadPriority (hThread, priority);
469
470         *p_handle = th;
471         return 0;
472     }
473     free (th);
474     return errno;
475 }
476
477 void vlc_join (vlc_thread_t handle, void **result)
478 {
479     do
480         vlc_testcancel ();
481     while (WaitForSingleObjectEx (handle->handle, INFINITE, TRUE)
482                                                         == WAIT_IO_COMPLETION);
483
484     CloseHandle (handle->handle);
485     if (result)
486         *result = handle->data;
487 #ifdef UNDER_CE
488     CloseHandle (handle->cancel_event);
489 #endif
490     free (handle);
491 }
492
493
494 /*** Thread cancellation ***/
495
496 /* APC procedure for thread cancellation */
497 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
498 {
499     (void)dummy;
500     vlc_control_cancel (VLC_DO_CANCEL);
501 }
502
503 void vlc_cancel (vlc_thread_t thread_id)
504 {
505 #ifndef UNDER_CE
506     QueueUserAPC (vlc_cancel_self, thread_id->handle, 0);
507 #else
508     SetEvent (thread_id->cancel_event);
509 #endif
510 }
511
512 int vlc_savecancel (void)
513 {
514     int state;
515
516     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
517     if (nfo == NULL)
518         return false; /* Main thread - cannot be cancelled anyway */
519
520     state = nfo->killable;
521     nfo->killable = false;
522     return state;
523 }
524
525 void vlc_restorecancel (int state)
526 {
527     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
528     assert (state == false || state == true);
529
530     if (nfo == NULL)
531         return; /* Main thread - cannot be cancelled anyway */
532
533     assert (!nfo->killable);
534     nfo->killable = state != 0;
535 }
536
537 void vlc_testcancel (void)
538 {
539     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
540     if (nfo == NULL)
541         return; /* Main thread - cannot be cancelled anyway */
542
543     if (nfo->killable && nfo->killed)
544     {
545         for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
546              p->proc (p->data);
547 #ifndef UNDER_CE
548         _endthread ();
549 #else
550         ExitThread(0);
551 #endif
552     }
553 }
554
555 void vlc_control_cancel (int cmd, ...)
556 {
557     /* NOTE: This function only modifies thread-specific data, so there is no
558      * need to lock anything. */
559     va_list ap;
560
561     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
562     if (nfo == NULL)
563         return; /* Main thread - cannot be cancelled anyway */
564
565     va_start (ap, cmd);
566     switch (cmd)
567     {
568         case VLC_DO_CANCEL:
569             nfo->killed = true;
570             break;
571
572         case VLC_CLEANUP_PUSH:
573         {
574             /* cleaner is a pointer to the caller stack, no need to allocate
575              * and copy anything. As a nice side effect, this cannot fail. */
576             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
577             cleaner->next = nfo->cleaners;
578             nfo->cleaners = cleaner;
579             break;
580         }
581
582         case VLC_CLEANUP_POP:
583         {
584             nfo->cleaners = nfo->cleaners->next;
585             break;
586         }
587     }
588     va_end (ap);
589 }
590
591
592 /*** Timers ***/
593 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
594 {
595     vlc_timer_t *id = val;
596
597     assert (timeout);
598     if (TryEnterCriticalSection (&id->serializer))
599     {
600         id->overrun = InterlockedExchange (&id->counter, 0);
601         id->func (id->data);
602         LeaveCriticalSection (&id->serializer);
603     }
604     else /* Overrun */
605         InterlockedIncrement (&id->counter);
606 }
607
608 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
609 {
610     id->func = func;
611     id->data = data;
612     id->overrun = 0;
613     id->handle = INVALID_HANDLE_VALUE;
614     InitializeCriticalSection (&id->serializer);
615     return 0;
616 }
617
618 void vlc_timer_destroy (vlc_timer_t *id)
619 {
620     if (id->handle != INVALID_HANDLE_VALUE)
621         DeleteTimerQueueTimer (NULL, id->handle, NULL);
622     DeleteCriticalSection (&id->serializer);
623 }
624
625 void vlc_timer_schedule (vlc_timer_t *id, bool absolute,
626                          mtime_t value, mtime_t interval)
627 {
628     if (id->handle != INVALID_HANDLE_VALUE)
629     {
630         DeleteTimerQueueTimer (NULL, id->handle, NULL);
631         id->handle = INVALID_HANDLE_VALUE;
632     }
633     if (value == 0)
634         return; /* Disarm */
635
636     if (absolute)
637         value -= mdate ();
638     value = (value + 999) / 1000;
639     interval = (interval + 999) / 1000;
640     if (!CreateTimerQueueTimer (&id->handle, NULL, vlc_timer_do, id, value,
641                                 interval, WT_EXECUTEDEFAULT))
642         abort ();
643 }
644
645 unsigned vlc_timer_getoverrun (const vlc_timer_t *id)
646 {
647     return id->overrun;
648 }