]> git.sesse.net Git - vlc/blob - src/misc/w32thread.c
7e2611f627e2a2bf51134c6437dbf31072272998
[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->writer != 0)
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->writers--;
343     lock->writer = GetCurrentThreadId ();
344     vlc_mutex_unlock (&lock->mutex);
345 }
346
347 /**
348  * Releases a read/write lock.
349  */
350 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
351 {
352     vlc_mutex_lock (&lock->mutex);
353     if (lock->readers > 0)
354         lock->readers--; /* Read unlock */
355     else
356         lock->writer = 0; /* Write unlock */
357
358     if (lock->writers > 0)
359     {
360         if (lock->readers == 0)
361             vlc_cond_signal (&lock->write_wait);
362     }
363     else
364         vlc_cond_broadcast (&lock->read_wait);
365     vlc_mutex_unlock (&lock->mutex);
366 }
367
368 /*** Thread-specific variables (TLS) ***/
369 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
370 {
371 #warning FIXME: use destr() callback and stop leaking!
372     *p_tls = TlsAlloc();
373     return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
374 }
375
376 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
377 {
378     TlsFree (*p_tls);
379 }
380
381 /**
382  * Sets a thread-local variable.
383  * @param key thread-local variable key (created with vlc_threadvar_create())
384  * @param value new value for the variable for the calling thread
385  * @return 0 on success, a system error code otherwise.
386  */
387 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
388 {
389     return TlsSetValue (key, value) ? ENOMEM : 0;
390 }
391
392 /**
393  * Gets the value of a thread-local variable for the calling thread.
394  * This function cannot fail.
395  * @return the value associated with the given variable for the calling
396  * or NULL if there is no value.
397  */
398 void *vlc_threadvar_get (vlc_threadvar_t key)
399 {
400     return TlsGetValue (key);
401 }
402
403
404 /*** Threads ***/
405 void vlc_threads_setup (libvlc_int_t *p_libvlc)
406 {
407     (void) p_libvlc;
408 }
409
410 struct vlc_entry_data
411 {
412     void * (*func) (void *);
413     void *  data;
414 };
415
416 static unsigned __stdcall vlc_entry (void *p)
417 {
418     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
419     struct vlc_entry_data data;
420
421     memcpy (&data, p, sizeof (data));
422     free (p);
423
424 #ifdef UNDER_CE
425     cancel_data.cancel_event = data.handle->cancel_event;
426 #endif
427
428     vlc_threadvar_set (cancel_key, &cancel_data);
429     data.func (data.data);
430     return 0;
431 }
432
433 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
434                int priority)
435 {
436     /* When using the MSVCRT C library you have to use the _beginthreadex
437      * function instead of CreateThread, otherwise you'll end up with
438      * memory leaks and the signal functions not working (see Microsoft
439      * Knowledge Base, article 104641) */
440     HANDLE hThread;
441
442     struct vlc_entry_data *entry_data = malloc (sizeof (*entry_data));
443     if (entry_data == NULL)
444         return ENOMEM;
445     entry_data->func = entry;
446     entry_data->data = data;
447
448 #if defined( UNDER_CE )
449     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
450     if (th->cancel_event == NULL)
451     {
452         free (entry_data);
453         return errno;
454     }
455     hThread = CreateThread (NULL, 128*1024, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
456 #else
457     hThread = (HANDLE)(uintptr_t)
458         _beginthreadex (NULL, 0, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
459 #endif
460
461     if (hThread)
462     {
463 #ifndef UNDER_CE
464         /* Thread closes the handle when exiting, duplicate it here
465          * to be on the safe side when joining. */
466         if (!DuplicateHandle (GetCurrentProcess (), hThread,
467                               GetCurrentProcess (), p_handle, 0, FALSE,
468                               DUPLICATE_SAME_ACCESS))
469         {
470             CloseHandle (hThread);
471             free (entry_data);
472             return ENOMEM;
473         }
474 #else
475         th->handle = hThread;
476 #endif
477
478         ResumeThread (hThread);
479         if (priority)
480             SetThreadPriority (hThread, priority);
481         return 0;
482     }
483
484 #ifdef UNDER_CE
485     CloseHandle (th->cancel_event);
486 #endif
487     free (entry_data);
488     return errno;
489 }
490
491 void vlc_join (vlc_thread_t handle, void **result)
492 {
493     do
494         vlc_testcancel ();
495     while (WaitForSingleObjectEx (handle, INFINITE, TRUE)
496                                                         == WAIT_IO_COMPLETION);
497
498     CloseHandle (handle);
499     assert (result == NULL); /* <- FIXME if ever needed */
500 #ifdef UNDER_CE
501     CloseHandle (handle->cancel_event);
502 #endif
503 }
504
505 void vlc_detach (vlc_thread_t handle)
506 {
507     CloseHandle (handle);
508 }
509
510 /*** Thread cancellation ***/
511
512 /* APC procedure for thread cancellation */
513 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
514 {
515     (void)dummy;
516     vlc_control_cancel (VLC_DO_CANCEL);
517 }
518
519 void vlc_cancel (vlc_thread_t thread_id)
520 {
521 #ifndef UNDER_CE
522     QueueUserAPC (vlc_cancel_self, thread_id, 0);
523 #else
524     SetEvent (thread_id->cancel_event);
525 #endif
526 }
527
528 int vlc_savecancel (void)
529 {
530     int state;
531
532     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
533     if (nfo == NULL)
534         return false; /* Main thread - cannot be cancelled anyway */
535
536     state = nfo->killable;
537     nfo->killable = false;
538     return state;
539 }
540
541 void vlc_restorecancel (int state)
542 {
543     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
544     assert (state == false || state == true);
545
546     if (nfo == NULL)
547         return; /* Main thread - cannot be cancelled anyway */
548
549     assert (!nfo->killable);
550     nfo->killable = state != 0;
551 }
552
553 void vlc_testcancel (void)
554 {
555     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
556     if (nfo == NULL)
557         return; /* Main thread - cannot be cancelled anyway */
558
559     if (nfo->killable && nfo->killed)
560     {
561         for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
562              p->proc (p->data);
563 #ifndef UNDER_CE
564         _endthread ();
565 #else
566         ExitThread(0);
567 #endif
568     }
569 }
570
571 void vlc_control_cancel (int cmd, ...)
572 {
573     /* NOTE: This function only modifies thread-specific data, so there is no
574      * need to lock anything. */
575     va_list ap;
576
577     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
578     if (nfo == NULL)
579         return; /* Main thread - cannot be cancelled anyway */
580
581     va_start (ap, cmd);
582     switch (cmd)
583     {
584         case VLC_DO_CANCEL:
585             nfo->killed = true;
586             break;
587
588         case VLC_CLEANUP_PUSH:
589         {
590             /* cleaner is a pointer to the caller stack, no need to allocate
591              * and copy anything. As a nice side effect, this cannot fail. */
592             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
593             cleaner->next = nfo->cleaners;
594             nfo->cleaners = cleaner;
595             break;
596         }
597
598         case VLC_CLEANUP_POP:
599         {
600             nfo->cleaners = nfo->cleaners->next;
601             break;
602         }
603     }
604     va_end (ap);
605 }
606
607
608 /*** Timers ***/
609 struct vlc_timer
610 {
611     HANDLE handle;
612     void (*func) (void *);
613     void *data;
614 };
615
616 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
617 {
618     struct vlc_timer *timer = val;
619
620     assert (timeout);
621     timer->func (timer->data);
622 }
623
624 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
625 {
626     struct vlc_timer *timer = malloc (sizeof (*timer));
627
628     if (timer == NULL)
629         return ENOMEM;
630     timer->func = func;
631     timer->data = data;
632     timer->handle = INVALID_HANDLE_VALUE;
633     *id = timer;
634     return 0;
635 }
636
637 void vlc_timer_destroy (vlc_timer_t timer)
638 {
639     if (timer->handle != INVALID_HANDLE_VALUE)
640         DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
641     free (timer);
642 }
643
644 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
645                          mtime_t value, mtime_t interval)
646 {
647     if (timer->handle != INVALID_HANDLE_VALUE)
648     {
649         DeleteTimerQueueTimer (NULL, timer->handle, NULL);
650         timer->handle = INVALID_HANDLE_VALUE;
651     }
652     if (value == 0)
653         return; /* Disarm */
654
655     if (absolute)
656         value -= mdate ();
657     value = (value + 999) / 1000;
658     interval = (interval + 999) / 1000;
659     if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
660                                 value, interval, WT_EXECUTEDEFAULT))
661         abort ();
662 }
663
664 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
665 {
666     (void)timer;
667     return 0;
668 }