add caching emits for dvb, dshow, screen and dvdread
[vlc.git] / src / misc / w32thread.c
blob3669a945c9c86ee640e02153e90f0384b29eb003
1 /*****************************************************************************
2 * w32thread.c : Win32 back-end for LibVLC
3 *****************************************************************************
4 * Copyright (C) 1999-2009 the VideoLAN team
5 * $Id$
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 * Pierre Ynard
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27 *****************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
33 #include <vlc_common.h>
35 #include "libvlc.h"
36 #include <stdarg.h>
37 #include <assert.h>
38 #include <limits.h>
39 #ifdef UNDER_CE
40 # include <mmsystem.h>
41 #endif
43 static vlc_threadvar_t cancel_key;
45 /**
46 * Per-thread cancellation data
48 typedef struct vlc_cancel_t
50 vlc_cleanup_t *cleaners;
51 #ifdef UNDER_CE
52 HANDLE cancel_event;
53 #endif
54 bool killable;
55 bool killed;
56 } vlc_cancel_t;
58 #ifndef UNDER_CE
59 # define VLC_CANCEL_INIT { NULL, true, false }
60 #else
61 # define VLC_CANCEL_INIT { NULL, NULL, true, false }
62 #endif
64 #ifdef UNDER_CE
65 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
67 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
68 DWORD delay)
70 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
71 if (nfo == NULL)
73 /* Main thread - cannot be cancelled anyway */
74 return WaitForMultipleObjects (count, handles, FALSE, delay);
76 HANDLE new_handles[count + 1];
77 memcpy(new_handles, handles, count * sizeof(HANDLE));
78 new_handles[count] = nfo->cancel_event;
79 DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
80 delay);
81 if (result == WAIT_OBJECT_0 + count)
83 vlc_cancel_self (NULL);
84 return WAIT_IO_COMPLETION;
86 else
88 return result;
92 DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
94 if (bAlertable)
96 DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
97 return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
99 else
101 Sleep(dwMilliseconds);
102 return 0;
106 DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
107 BOOL bAlertable)
109 if (bAlertable)
111 /* The MSDN documentation specifies different return codes,
112 * but in practice they are the same. We just check that it
113 * remains so. */
114 #if WAIT_ABANDONED != WAIT_ABANDONED_0
115 # error Windows headers changed, code needs to be rewritten!
116 #endif
117 return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
119 else
121 return WaitForSingleObject (hHandle, dwMilliseconds);
125 DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
126 BOOL bWaitAll, DWORD dwMilliseconds,
127 BOOL bAlertable)
129 if (bAlertable)
131 /* We do not support the bWaitAll case */
132 assert (! bWaitAll);
133 return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
135 else
137 return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
138 dwMilliseconds);
141 #endif
143 static vlc_mutex_t super_mutex;
145 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
147 (void) hinstDll;
148 (void) lpvReserved;
150 switch (fdwReason)
152 case DLL_PROCESS_ATTACH:
153 vlc_mutex_init (&super_mutex);
154 vlc_threadvar_create (&cancel_key, free);
155 break;
157 case DLL_PROCESS_DETACH:
158 vlc_threadvar_delete( &cancel_key );
159 vlc_mutex_destroy (&super_mutex);
160 break;
162 return TRUE;
165 /*** Mutexes ***/
166 void vlc_mutex_init( vlc_mutex_t *p_mutex )
168 /* This creates a recursive mutex. This is OK as fast mutexes have
169 * no defined behavior in case of recursive locking. */
170 InitializeCriticalSection (&p_mutex->mutex);
171 p_mutex->initialized = 1;
174 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
176 InitializeCriticalSection( &p_mutex->mutex );
177 p_mutex->initialized = 1;
181 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
183 assert (InterlockedExchange (&p_mutex->initialized, -1) == 1);
184 DeleteCriticalSection (&p_mutex->mutex);
187 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
189 if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
190 { /* ^^ We could also lock super_mutex all the time... sluggish */
191 assert (p_mutex != &super_mutex); /* this one cannot be static */
193 vlc_mutex_lock (&super_mutex);
194 if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
195 vlc_mutex_init (p_mutex);
196 /* FIXME: destroy the mutex some time... */
197 vlc_mutex_unlock (&super_mutex);
199 assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
200 EnterCriticalSection (&p_mutex->mutex);
203 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
205 if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
206 { /* ^^ We could also lock super_mutex all the time... sluggish */
207 assert (p_mutex != &super_mutex); /* this one cannot be static */
209 vlc_mutex_lock (&super_mutex);
210 if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
211 vlc_mutex_init (p_mutex);
212 /* FIXME: destroy the mutex some time... */
213 vlc_mutex_unlock (&super_mutex);
215 assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
216 return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
219 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
221 assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
222 LeaveCriticalSection (&p_mutex->mutex);
225 /*** Condition variables ***/
226 void vlc_cond_init( vlc_cond_t *p_condvar )
228 /* Create a manual-reset event (manual reset is needed for broadcast). */
229 *p_condvar = CreateEvent (NULL, TRUE, FALSE, NULL);
230 if (!*p_condvar)
231 abort();
234 void vlc_cond_destroy (vlc_cond_t *p_condvar)
236 CloseHandle (*p_condvar);
239 void vlc_cond_signal (vlc_cond_t *p_condvar)
241 /* NOTE: This will cause a broadcast, that is wrong.
242 * This will also wake up the next waiting thread if no threads are yet
243 * waiting, which is also wrong. However both of these issues are allowed
244 * by the provision for spurious wakeups. Better have too many wakeups
245 * than too few (= deadlocks). */
246 SetEvent (*p_condvar);
249 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
251 SetEvent (*p_condvar);
254 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
256 DWORD result;
260 vlc_testcancel ();
261 LeaveCriticalSection (&p_mutex->mutex);
262 result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE);
263 EnterCriticalSection (&p_mutex->mutex);
265 while (result == WAIT_IO_COMPLETION);
267 assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
268 assert (result != WAIT_FAILED);
269 ResetEvent (*p_condvar);
272 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
273 mtime_t deadline)
275 DWORD result;
279 vlc_testcancel ();
281 mtime_t total = (deadline - mdate ())/1000;
282 if( total < 0 )
283 total = 0;
285 DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
286 LeaveCriticalSection (&p_mutex->mutex);
287 result = WaitForSingleObjectEx (*p_condvar, delay, TRUE);
288 EnterCriticalSection (&p_mutex->mutex);
290 while (result == WAIT_IO_COMPLETION);
292 assert (result != WAIT_ABANDONED);
293 assert (result != WAIT_FAILED);
294 ResetEvent (*p_condvar);
296 return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
299 /*** Read/write locks */
300 /* SRW (Slim Read Write) locks are available in Vista+ only */
301 void vlc_rwlock_init (vlc_rwlock_t *lock)
303 vlc_mutex_init (&lock->mutex);
304 vlc_cond_init (&lock->read_wait);
305 vlc_cond_init (&lock->write_wait);
306 lock->readers = 0; /* active readers */
307 lock->writers = 0; /* waiting or active writers */
308 lock->writer = 0; /* ID of active writer */
312 * Destroys an initialized unused read/write lock.
314 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
316 vlc_cond_destroy (&lock->read_wait);
317 vlc_cond_destroy (&lock->write_wait);
318 vlc_mutex_destroy (&lock->mutex);
322 * Acquires a read/write lock for reading. Recursion is allowed.
324 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
326 vlc_mutex_lock (&lock->mutex);
327 while (lock->writer != 0)
328 vlc_cond_wait (&lock->read_wait, &lock->mutex);
329 if (lock->readers == ULONG_MAX)
330 abort ();
331 lock->readers++;
332 vlc_mutex_unlock (&lock->mutex);
336 * Acquires a read/write lock for writing. Recursion is not allowed.
338 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
340 vlc_mutex_lock (&lock->mutex);
341 if (lock->writers == ULONG_MAX)
342 abort ();
343 lock->writers++;
344 while ((lock->readers > 0) || (lock->writer != 0))
345 vlc_cond_wait (&lock->write_wait, &lock->mutex);
346 lock->writers--;
347 lock->writer = GetCurrentThreadId ();
348 vlc_mutex_unlock (&lock->mutex);
352 * Releases a read/write lock.
354 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
356 vlc_mutex_lock (&lock->mutex);
357 if (lock->readers > 0)
358 lock->readers--; /* Read unlock */
359 else
360 lock->writer = 0; /* Write unlock */
362 if (lock->writers > 0)
364 if (lock->readers == 0)
365 vlc_cond_signal (&lock->write_wait);
367 else
368 vlc_cond_broadcast (&lock->read_wait);
369 vlc_mutex_unlock (&lock->mutex);
372 /*** Thread-specific variables (TLS) ***/
373 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
375 #warning FIXME: use destr() callback and stop leaking!
376 VLC_UNUSED( destr );
378 *p_tls = TlsAlloc();
379 return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
382 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
384 TlsFree (*p_tls);
388 * Sets a thread-local variable.
389 * @param key thread-local variable key (created with vlc_threadvar_create())
390 * @param value new value for the variable for the calling thread
391 * @return 0 on success, a system error code otherwise.
393 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
395 return TlsSetValue (key, value) ? ENOMEM : 0;
399 * Gets the value of a thread-local variable for the calling thread.
400 * This function cannot fail.
401 * @return the value associated with the given variable for the calling
402 * or NULL if there is no value.
404 void *vlc_threadvar_get (vlc_threadvar_t key)
406 return TlsGetValue (key);
410 /*** Threads ***/
411 void vlc_threads_setup (libvlc_int_t *p_libvlc)
413 (void) p_libvlc;
416 struct vlc_entry_data
418 void * (*func) (void *);
419 void * data;
420 #ifdef UNDER_CE
421 HANDLE cancel_event;
422 #endif
425 static unsigned __stdcall vlc_entry (void *p)
427 vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
428 struct vlc_entry_data data;
430 memcpy (&data, p, sizeof (data));
431 free (p);
433 #ifdef UNDER_CE
434 cancel_data.cancel_event = data.cancel_event;
435 #endif
437 vlc_threadvar_set (cancel_key, &cancel_data);
438 data.func (data.data);
439 return 0;
442 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
443 int priority)
445 int err = ENOMEM;
446 HANDLE hThread;
448 struct vlc_entry_data *entry_data = malloc (sizeof (*entry_data));
449 if (entry_data == NULL)
450 return ENOMEM;
451 entry_data->func = entry;
452 entry_data->data = data;
454 #ifndef UNDER_CE
455 /* When using the MSVCRT C library you have to use the _beginthreadex
456 * function instead of CreateThread, otherwise you'll end up with
457 * memory leaks and the signal functions not working (see Microsoft
458 * Knowledge Base, article 104641) */
459 hThread = (HANDLE)(uintptr_t)
460 _beginthreadex (NULL, 0, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
461 if (! hThread)
463 err = errno;
464 goto error;
467 /* Thread closes the handle when exiting, duplicate it here
468 * to be on the safe side when joining. */
469 if (!DuplicateHandle (GetCurrentProcess (), hThread,
470 GetCurrentProcess (), p_handle, 0, FALSE,
471 DUPLICATE_SAME_ACCESS))
473 CloseHandle (hThread);
474 goto error;
477 #else
478 vlc_thread_t th = malloc (sizeof (*th));
479 if (th == NULL)
480 goto error;
481 th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
482 if (th->cancel_event == NULL)
484 free (th);
485 goto error;
487 entry_data->cancel_event = th->cancel_event;
489 /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
490 * Thread handles act up, too. */
491 th->handle = CreateThread (NULL, 128*1024, vlc_entry, entry_data,
492 CREATE_SUSPENDED, NULL);
493 if (th->handle == NULL)
495 CloseHandle (th->cancel_event);
496 free (th);
497 goto error;
500 *p_handle = th;
501 hThread = th->handle;
503 #endif
505 ResumeThread (hThread);
506 if (priority)
507 SetThreadPriority (hThread, priority);
509 return 0;
511 error:
512 free (entry_data);
513 return err;
516 void vlc_join (vlc_thread_t handle, void **result)
518 #ifdef UNDER_CE
519 # define handle handle->handle
520 #endif
522 vlc_testcancel ();
523 while (WaitForSingleObjectEx (handle, INFINITE, TRUE)
524 == WAIT_IO_COMPLETION);
526 CloseHandle (handle);
527 assert (result == NULL); /* <- FIXME if ever needed */
528 #ifdef UNDER_CE
529 # undef handle
530 CloseHandle (handle->cancel_event);
531 free (handle);
532 #endif
535 void vlc_detach (vlc_thread_t handle)
537 #ifndef UNDER_CE
538 CloseHandle (handle);
539 #else
540 /* FIXME: handle->cancel_event leak */
541 CloseHandle (handle->handle);
542 free (handle);
543 #endif
546 /*** Thread cancellation ***/
548 /* APC procedure for thread cancellation */
549 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
551 (void)dummy;
552 vlc_control_cancel (VLC_DO_CANCEL);
555 void vlc_cancel (vlc_thread_t thread_id)
557 #ifndef UNDER_CE
558 QueueUserAPC (vlc_cancel_self, thread_id, 0);
559 #else
560 SetEvent (thread_id->cancel_event);
561 #endif
564 int vlc_savecancel (void)
566 int state;
568 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
569 if (nfo == NULL)
570 return false; /* Main thread - cannot be cancelled anyway */
572 state = nfo->killable;
573 nfo->killable = false;
574 return state;
577 void vlc_restorecancel (int state)
579 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
580 assert (state == false || state == true);
582 if (nfo == NULL)
583 return; /* Main thread - cannot be cancelled anyway */
585 assert (!nfo->killable);
586 nfo->killable = state != 0;
589 void vlc_testcancel (void)
591 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
592 if (nfo == NULL)
593 return; /* Main thread - cannot be cancelled anyway */
595 if (nfo->killable && nfo->killed)
597 for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
598 p->proc (p->data);
599 #ifndef UNDER_CE
600 _endthread ();
601 #else
602 ExitThread(0);
603 #endif
607 void vlc_control_cancel (int cmd, ...)
609 /* NOTE: This function only modifies thread-specific data, so there is no
610 * need to lock anything. */
611 va_list ap;
613 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
614 if (nfo == NULL)
615 return; /* Main thread - cannot be cancelled anyway */
617 va_start (ap, cmd);
618 switch (cmd)
620 case VLC_DO_CANCEL:
621 nfo->killed = true;
622 break;
624 case VLC_CLEANUP_PUSH:
626 /* cleaner is a pointer to the caller stack, no need to allocate
627 * and copy anything. As a nice side effect, this cannot fail. */
628 vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
629 cleaner->next = nfo->cleaners;
630 nfo->cleaners = cleaner;
631 break;
634 case VLC_CLEANUP_POP:
636 nfo->cleaners = nfo->cleaners->next;
637 break;
640 va_end (ap);
644 /*** Timers ***/
645 struct vlc_timer
647 #ifndef UNDER_CE
648 HANDLE handle;
649 #else
650 unsigned id;
651 unsigned interval;
652 #endif
653 void (*func) (void *);
654 void *data;
657 #ifndef UNDER_CE
658 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
660 struct vlc_timer *timer = val;
662 assert (timeout);
663 timer->func (timer->data);
665 #else
666 static void CALLBACK vlc_timer_do (unsigned timer_id, unsigned msg,
667 DWORD_PTR user, DWORD_PTR unused1,
668 DWORD_PTR unused2)
670 struct vlc_timer *timer = (struct vlc_timer *) user;
671 assert (timer_id == timer->id);
672 (void) msg;
673 (void) unused1;
674 (void) unused2;
676 timer->func (timer->data);
678 if (timer->interval)
680 mtime_t interval = timer->interval * 1000;
681 vlc_timer_schedule (timer, false, interval, interval);
684 #endif
686 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
688 struct vlc_timer *timer = malloc (sizeof (*timer));
690 if (timer == NULL)
691 return ENOMEM;
692 timer->func = func;
693 timer->data = data;
694 #ifndef UNDER_CE
695 timer->handle = INVALID_HANDLE_VALUE;
696 #else
697 timer->id = 0;
698 timer->interval = 0;
699 #endif
700 *id = timer;
701 return 0;
704 void vlc_timer_destroy (vlc_timer_t timer)
706 #ifndef UNDER_CE
707 if (timer->handle != INVALID_HANDLE_VALUE)
708 DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
709 #else
710 if (timer->id)
711 timeKillEvent (timer->id);
712 /* FIXME: timers that have not yet completed will trigger use-after-free */
713 #endif
714 free (timer);
717 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
718 mtime_t value, mtime_t interval)
720 #ifndef UNDER_CE
721 if (timer->handle != INVALID_HANDLE_VALUE)
723 DeleteTimerQueueTimer (NULL, timer->handle, NULL);
724 timer->handle = INVALID_HANDLE_VALUE;
726 #else
727 if (timer->id)
729 timeKillEvent (timer->id);
730 timer->id = 0;
731 timer->interval = 0;
733 #endif
734 if (value == 0)
735 return; /* Disarm */
737 if (absolute)
738 value -= mdate ();
739 value = (value + 999) / 1000;
740 interval = (interval + 999) / 1000;
742 #ifndef UNDER_CE
743 if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
744 value, interval, WT_EXECUTEDEFAULT))
745 #else
746 TIMECAPS caps;
747 timeGetDevCaps (&caps, sizeof(caps));
749 unsigned delay = value;
750 delay = __MAX(delay, caps.wPeriodMin);
751 delay = __MIN(delay, caps.wPeriodMax);
753 unsigned event = TIME_ONESHOT;
755 if (interval == delay)
756 event = TIME_PERIODIC;
757 else if (interval)
758 timer->interval = interval;
760 timer->id = timeSetEvent (delay, delay / 20, vlc_timer_do, (DWORD) timer,
761 event);
762 if (!timer->id)
763 #endif
764 abort ();
767 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
769 (void)timer;
770 return 0;