]> git.sesse.net Git - vlc/blob - src/misc/w32thread.c
vlc_clone(): abide by --rt-priority and --rt-offset
[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 static unsigned __stdcall vlc_entry (void *data)
411 {
412     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
413     vlc_thread_t self = data;
414 #ifdef UNDER_CE
415     cancel_data.cancel_event = self->cancel_event;
416 #endif
417
418     vlc_threadvar_set (cancel_key, &cancel_data);
419     self->data = self->entry (self->data);
420     return 0;
421 }
422
423 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
424                int priority)
425 {
426     /* When using the MSVCRT C library you have to use the _beginthreadex
427      * function instead of CreateThread, otherwise you'll end up with
428      * memory leaks and the signal functions not working (see Microsoft
429      * Knowledge Base, article 104641) */
430     HANDLE hThread;
431     vlc_thread_t th = malloc (sizeof (*th));
432
433     if (th == NULL)
434         return ENOMEM;
435
436     th->data = data;
437     th->entry = entry;
438 #if defined( UNDER_CE )
439     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
440     if (th->cancel_event == NULL)
441     {
442         free(th);
443         return errno;
444     }
445     hThread = CreateThread (NULL, 128*1024, vlc_entry, th, CREATE_SUSPENDED, NULL);
446 #else
447     hThread = (HANDLE)(uintptr_t)
448         _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
449 #endif
450
451     if (hThread)
452     {
453 #ifndef UNDER_CE
454         /* Thread closes the handle when exiting, duplicate it here
455          * to be on the safe side when joining. */
456         if (!DuplicateHandle (GetCurrentProcess (), hThread,
457                               GetCurrentProcess (), &th->handle, 0, FALSE,
458                               DUPLICATE_SAME_ACCESS))
459         {
460             CloseHandle (hThread);
461             free (th);
462             return ENOMEM;
463         }
464 #else
465         th->handle = hThread;
466 #endif
467
468         ResumeThread (hThread);
469         if (priority)
470             SetThreadPriority (hThread, priority);
471
472         *p_handle = th;
473         return 0;
474     }
475     free (th);
476     return errno;
477 }
478
479 void vlc_join (vlc_thread_t handle, void **result)
480 {
481     do
482         vlc_testcancel ();
483     while (WaitForSingleObjectEx (handle->handle, INFINITE, TRUE)
484                                                         == WAIT_IO_COMPLETION);
485
486     CloseHandle (handle->handle);
487     if (result)
488         *result = handle->data;
489 #ifdef UNDER_CE
490     CloseHandle (handle->cancel_event);
491 #endif
492     free (handle);
493 }
494
495
496 /*** Thread cancellation ***/
497
498 /* APC procedure for thread cancellation */
499 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
500 {
501     (void)dummy;
502     vlc_control_cancel (VLC_DO_CANCEL);
503 }
504
505 void vlc_cancel (vlc_thread_t thread_id)
506 {
507 #ifndef UNDER_CE
508     QueueUserAPC (vlc_cancel_self, thread_id->handle, 0);
509 #else
510     SetEvent (thread_id->cancel_event);
511 #endif
512 }
513
514 int vlc_savecancel (void)
515 {
516     int state;
517
518     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
519     if (nfo == NULL)
520         return false; /* Main thread - cannot be cancelled anyway */
521
522     state = nfo->killable;
523     nfo->killable = false;
524     return state;
525 }
526
527 void vlc_restorecancel (int state)
528 {
529     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
530     assert (state == false || state == true);
531
532     if (nfo == NULL)
533         return; /* Main thread - cannot be cancelled anyway */
534
535     assert (!nfo->killable);
536     nfo->killable = state != 0;
537 }
538
539 void vlc_testcancel (void)
540 {
541     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
542     if (nfo == NULL)
543         return; /* Main thread - cannot be cancelled anyway */
544
545     if (nfo->killable && nfo->killed)
546     {
547         for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
548              p->proc (p->data);
549 #ifndef UNDER_CE
550         _endthread ();
551 #else
552         ExitThread(0);
553 #endif
554     }
555 }
556
557 void vlc_control_cancel (int cmd, ...)
558 {
559     /* NOTE: This function only modifies thread-specific data, so there is no
560      * need to lock anything. */
561     va_list ap;
562
563     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
564     if (nfo == NULL)
565         return; /* Main thread - cannot be cancelled anyway */
566
567     va_start (ap, cmd);
568     switch (cmd)
569     {
570         case VLC_DO_CANCEL:
571             nfo->killed = true;
572             break;
573
574         case VLC_CLEANUP_PUSH:
575         {
576             /* cleaner is a pointer to the caller stack, no need to allocate
577              * and copy anything. As a nice side effect, this cannot fail. */
578             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
579             cleaner->next = nfo->cleaners;
580             nfo->cleaners = cleaner;
581             break;
582         }
583
584         case VLC_CLEANUP_POP:
585         {
586             nfo->cleaners = nfo->cleaners->next;
587             break;
588         }
589     }
590     va_end (ap);
591 }
592
593
594 /*** Timers ***/
595 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
596 {
597     vlc_timer_t *id = val;
598
599     assert (timeout);
600     id->func (id->data);
601 }
602
603 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
604 {
605     id->func = func;
606     id->data = data;
607     id->handle = INVALID_HANDLE_VALUE;
608     return 0;
609 }
610
611 void vlc_timer_destroy (vlc_timer_t *id)
612 {
613     if (id->handle != INVALID_HANDLE_VALUE)
614         DeleteTimerQueueTimer (NULL, id->handle, NULL);
615 }
616
617 void vlc_timer_schedule (vlc_timer_t *id, bool absolute,
618                          mtime_t value, mtime_t interval)
619 {
620     if (id->handle != INVALID_HANDLE_VALUE)
621     {
622         DeleteTimerQueueTimer (NULL, id->handle, NULL);
623         id->handle = INVALID_HANDLE_VALUE;
624     }
625     if (value == 0)
626         return; /* Disarm */
627
628     if (absolute)
629         value -= mdate ();
630     value = (value + 999) / 1000;
631     interval = (interval + 999) / 1000;
632     if (!CreateTimerQueueTimer (&id->handle, NULL, vlc_timer_do, id, value,
633                                 interval, WT_EXECUTEDEFAULT))
634         abort ();
635 }
636
637 unsigned vlc_timer_getoverrun (const vlc_timer_t *id)
638 {
639     (void)id;
640     return 0;
641 }