1 /*****************************************************************************
2 * thread.c : Win32 back-end for LibVLC
3 *****************************************************************************
4 * Copyright (C) 1999-2009 the VideoLAN team
6 * Authors: Jean-Marc Dressler <polux@via.ecp.fr>
7 * Samuel Hocevar <sam@zoy.org>
8 * Gildas Bazin <gbazin@netcourrier.com>
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.
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.
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 *****************************************************************************/
32 #include <vlc_common.h>
40 # include <mmsystem.h>
43 static vlc_threadvar_t thread_key;
58 vlc_cleanup_t *cleaners;
60 void *(*entry) (void *);
65 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
67 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
70 struct vlc_thread *th = vlc_threadvar_get (thread_key);
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] = th->cancel_event;
79 DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
81 if (result == WAIT_OBJECT_0 + count)
83 vlc_cancel_self ((uintptr_t)th);
84 return WAIT_IO_COMPLETION;
92 DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
96 DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
97 return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
101 Sleep(dwMilliseconds);
106 DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
111 /* The MSDN documentation specifies different return codes,
112 * but in practice they are the same. We just check that it
114 #if WAIT_ABANDONED != WAIT_ABANDONED_0
115 # error Windows headers changed, code needs to be rewritten!
117 return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
121 return WaitForSingleObject (hHandle, dwMilliseconds);
125 DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
126 BOOL bWaitAll, DWORD dwMilliseconds,
131 /* We do not support the bWaitAll case */
133 return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
137 return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
143 vlc_mutex_t super_mutex;
144 vlc_cond_t super_variable;
146 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
153 case DLL_PROCESS_ATTACH:
154 vlc_mutex_init (&super_mutex);
155 vlc_cond_init (&super_variable);
156 vlc_threadvar_create (&thread_key, NULL);
159 case DLL_PROCESS_DETACH:
160 vlc_threadvar_delete (&thread_key);
161 vlc_cond_destroy (&super_variable);
162 vlc_mutex_destroy (&super_mutex);
169 void vlc_mutex_init( vlc_mutex_t *p_mutex )
171 /* This creates a recursive mutex. This is OK as fast mutexes have
172 * no defined behavior in case of recursive locking. */
173 InitializeCriticalSection (&p_mutex->mutex);
174 p_mutex->dynamic = true;
177 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
179 InitializeCriticalSection( &p_mutex->mutex );
180 p_mutex->dynamic = true;
184 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
186 assert (p_mutex->dynamic);
187 DeleteCriticalSection (&p_mutex->mutex);
190 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
192 if (!p_mutex->dynamic)
193 { /* static mutexes */
194 int canc = vlc_savecancel ();
195 assert (p_mutex != &super_mutex); /* this one cannot be static */
197 vlc_mutex_lock (&super_mutex);
198 while (p_mutex->locked)
200 p_mutex->contention++;
201 vlc_cond_wait (&super_variable, &super_mutex);
202 p_mutex->contention--;
204 p_mutex->locked = true;
205 vlc_mutex_unlock (&super_mutex);
206 vlc_restorecancel (canc);
210 EnterCriticalSection (&p_mutex->mutex);
213 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
215 if (!p_mutex->dynamic)
216 { /* static mutexes */
219 assert (p_mutex != &super_mutex); /* this one cannot be static */
220 vlc_mutex_lock (&super_mutex);
221 if (!p_mutex->locked)
223 p_mutex->locked = true;
226 vlc_mutex_unlock (&super_mutex);
230 return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
233 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
235 if (!p_mutex->dynamic)
236 { /* static mutexes */
237 assert (p_mutex != &super_mutex); /* this one cannot be static */
239 vlc_mutex_lock (&super_mutex);
240 assert (p_mutex->locked);
241 p_mutex->locked = false;
242 if (p_mutex->contention)
243 vlc_cond_broadcast (&super_variable);
244 vlc_mutex_unlock (&super_mutex);
248 LeaveCriticalSection (&p_mutex->mutex);
251 /*** Condition variables ***/
258 static void vlc_cond_init_common (vlc_cond_t *p_condvar, unsigned clock)
260 /* Create a manual-reset event (manual reset is needed for broadcast). */
261 p_condvar->handle = CreateEvent (NULL, TRUE, FALSE, NULL);
262 if (!p_condvar->handle)
264 p_condvar->clock = clock;
267 void vlc_cond_init (vlc_cond_t *p_condvar)
269 vlc_cond_init_common (p_condvar, CLOCK_MONOTONIC);
272 void vlc_cond_init_daytime (vlc_cond_t *p_condvar)
274 vlc_cond_init_common (p_condvar, CLOCK_REALTIME);
277 void vlc_cond_destroy (vlc_cond_t *p_condvar)
279 CloseHandle (p_condvar->handle);
282 void vlc_cond_signal (vlc_cond_t *p_condvar)
284 /* NOTE: This will cause a broadcast, that is wrong.
285 * This will also wake up the next waiting thread if no threads are yet
286 * waiting, which is also wrong. However both of these issues are allowed
287 * by the provision for spurious wakeups. Better have too many wakeups
288 * than too few (= deadlocks). */
289 SetEvent (p_condvar->handle);
292 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
294 SetEvent (p_condvar->handle);
297 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
301 assert (p_mutex->dynamic); /* TODO */
305 LeaveCriticalSection (&p_mutex->mutex);
306 result = WaitForSingleObjectEx (p_condvar->handle, INFINITE, TRUE);
307 EnterCriticalSection (&p_mutex->mutex);
309 while (result == WAIT_IO_COMPLETION);
311 assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
312 assert (result != WAIT_FAILED);
313 ResetEvent (p_condvar->handle);
316 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
321 assert (p_mutex->dynamic); /* TODO */
327 switch (p_condvar->clock)
329 case CLOCK_MONOTONIC:
332 case CLOCK_REALTIME: /* FIXME? sub-second precision */
333 total = CLOCK_FREQ * time (NULL);
338 total = (deadline - total) / 1000;
342 DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
343 LeaveCriticalSection (&p_mutex->mutex);
344 result = WaitForSingleObjectEx (p_condvar->handle, delay, TRUE);
345 EnterCriticalSection (&p_mutex->mutex);
347 while (result == WAIT_IO_COMPLETION);
349 assert (result != WAIT_ABANDONED);
350 assert (result != WAIT_FAILED);
351 ResetEvent (p_condvar->handle);
353 return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
357 void vlc_sem_init (vlc_sem_t *sem, unsigned value)
359 *sem = CreateSemaphore (NULL, value, 0x7fffffff, NULL);
364 void vlc_sem_destroy (vlc_sem_t *sem)
369 int vlc_sem_post (vlc_sem_t *sem)
371 ReleaseSemaphore (*sem, 1, NULL);
372 return 0; /* FIXME */
375 void vlc_sem_wait (vlc_sem_t *sem)
382 result = WaitForSingleObjectEx (*sem, INFINITE, TRUE);
384 while (result == WAIT_IO_COMPLETION);
387 /*** Read/write locks */
388 /* SRW (Slim Read Write) locks are available in Vista+ only */
389 void vlc_rwlock_init (vlc_rwlock_t *lock)
391 vlc_mutex_init (&lock->mutex);
392 vlc_cond_init (&lock->read_wait);
393 vlc_cond_init (&lock->write_wait);
394 lock->readers = 0; /* active readers */
395 lock->writers = 0; /* waiting writers */
396 lock->writer = 0; /* ID of active writer */
399 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
401 vlc_cond_destroy (&lock->read_wait);
402 vlc_cond_destroy (&lock->write_wait);
403 vlc_mutex_destroy (&lock->mutex);
406 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
408 vlc_mutex_lock (&lock->mutex);
409 /* Recursive read-locking is allowed. With the infos available:
410 * - the loosest possible condition (no active writer) is:
411 * (lock->writer != 0)
412 * - the strictest possible condition is:
413 * (lock->writer != 0 || (lock->readers == 0 && lock->writers > 0))
414 * or (lock->readers == 0 && (lock->writer != 0 || lock->writers > 0))
416 while (lock->writer != 0)
418 assert (lock->readers == 0);
419 vlc_cond_wait (&lock->read_wait, &lock->mutex);
421 if (unlikely(lock->readers == ULONG_MAX))
424 vlc_mutex_unlock (&lock->mutex);
427 static void vlc_rwlock_rdunlock (vlc_rwlock_t *lock)
429 vlc_mutex_lock (&lock->mutex);
430 assert (lock->readers > 0);
432 /* If there are no readers left, wake up a writer. */
433 if (--lock->readers == 0 && lock->writers > 0)
434 vlc_cond_signal (&lock->write_wait);
435 vlc_mutex_unlock (&lock->mutex);
438 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
440 vlc_mutex_lock (&lock->mutex);
441 if (unlikely(lock->writers == ULONG_MAX))
444 /* Wait until nobody owns the lock in either way. */
445 while ((lock->readers > 0) || (lock->writer != 0))
446 vlc_cond_wait (&lock->write_wait, &lock->mutex);
448 assert (lock->writer == 0);
449 lock->writer = GetCurrentThreadId ();
450 vlc_mutex_unlock (&lock->mutex);
453 static void vlc_rwlock_wrunlock (vlc_rwlock_t *lock)
455 vlc_mutex_lock (&lock->mutex);
456 assert (lock->writer == GetCurrentThreadId ());
457 assert (lock->readers == 0);
458 lock->writer = 0; /* Write unlock */
460 /* Let reader and writer compete. Scheduler decides who wins. */
461 if (lock->writers > 0)
462 vlc_cond_signal (&lock->write_wait);
463 vlc_cond_broadcast (&lock->read_wait);
464 vlc_mutex_unlock (&lock->mutex);
467 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
469 /* Note: If the lock is held for reading, lock->writer is nul.
470 * If the lock is held for writing, only this thread can store a value to
471 * lock->writer. Either way, lock->writer is safe to fetch here. */
472 if (lock->writer != 0)
473 vlc_rwlock_wrunlock (lock);
475 vlc_rwlock_rdunlock (lock);
478 /*** Thread-specific variables (TLS) ***/
482 void (*destroy) (void *);
483 struct vlc_threadvar *prev;
484 struct vlc_threadvar *next;
485 } *vlc_threadvar_last = NULL;
487 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
489 struct vlc_threadvar *var = malloc (sizeof (*var));
490 if (unlikely(var == NULL))
493 var->id = TlsAlloc();
494 if (var->id == TLS_OUT_OF_INDEXES)
499 var->destroy = destr;
503 vlc_mutex_lock (&super_mutex);
504 var->prev = vlc_threadvar_last;
505 vlc_threadvar_last = var;
506 vlc_mutex_unlock (&super_mutex);
510 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
512 struct vlc_threadvar *var = *p_tls;
514 vlc_mutex_lock (&super_mutex);
515 if (var->prev != NULL)
516 var->prev->next = var->next;
518 vlc_threadvar_last = var->next;
519 if (var->next != NULL)
520 var->next->prev = var->prev;
521 vlc_mutex_unlock (&super_mutex);
527 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
529 return TlsSetValue (key->id, value) ? ENOMEM : 0;
532 void *vlc_threadvar_get (vlc_threadvar_t key)
534 return TlsGetValue (key->id);
538 void vlc_threads_setup (libvlc_int_t *p_libvlc)
543 static void vlc_thread_cleanup (struct vlc_thread *th)
548 /* TODO: use RW lock or something similar */
549 vlc_mutex_lock (&super_mutex);
550 for (key = vlc_threadvar_last; key != NULL; key = key->prev)
552 void *value = vlc_threadvar_get (key);
553 if (value != NULL && key->destroy != NULL)
555 vlc_mutex_unlock (&super_mutex);
556 vlc_threadvar_set (key, NULL);
557 key->destroy (value);
561 vlc_mutex_unlock (&super_mutex);
565 CloseHandle (th->id);
567 CloseHandle (th->cancel_event);
573 static unsigned __stdcall vlc_entry (void *p)
575 struct vlc_thread *th = p;
577 vlc_threadvar_set (thread_key, th);
579 th->data = th->entry (th->data);
580 vlc_thread_cleanup (th);
584 static int vlc_clone_attr (vlc_thread_t *p_handle, bool detached,
585 void *(*entry) (void *), void *data, int priority)
587 struct vlc_thread *th = malloc (sizeof (*th));
588 if (unlikely(th == NULL))
592 th->detached = detached;
593 th->killable = false; /* not until vlc_entry() ! */
599 /* When using the MSVCRT C library you have to use the _beginthreadex
600 * function instead of CreateThread, otherwise you'll end up with
601 * memory leaks and the signal functions not working (see Microsoft
602 * Knowledge Base, article 104641) */
603 hThread = (HANDLE)(uintptr_t)
604 _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
613 th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
614 if (th->cancel_event == NULL)
620 /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
621 * Thread handles act up, too. */
622 hThread = CreateThread (NULL, 128*1024, vlc_entry, th,
623 CREATE_SUSPENDED, NULL);
626 CloseHandle (th->cancel_event);
632 /* Thread is suspended, so we can safely set th->id */
634 if (p_handle != NULL)
638 SetThreadPriority (hThread, priority);
640 ResumeThread (hThread);
645 int vlc_clone (vlc_thread_t *p_handle, void *(*entry) (void *),
646 void *data, int priority)
648 return vlc_clone_attr (p_handle, false, entry, data, priority);
651 void vlc_join (vlc_thread_t th, void **result)
655 while (WaitForSingleObjectEx (th->id, INFINITE, TRUE)
656 == WAIT_IO_COMPLETION);
660 CloseHandle (th->id);
662 CloseHandle (th->cancel_event);
667 int vlc_clone_detach (vlc_thread_t *p_handle, void *(*entry) (void *),
668 void *data, int priority)
671 if (p_handle == NULL)
674 return vlc_clone_attr (p_handle, true, entry, data, priority);
677 int vlc_set_priority (vlc_thread_t th, int priority)
679 if (!SetThreadPriority (th->id, priority))
684 /*** Thread cancellation ***/
686 /* APC procedure for thread cancellation */
687 static void CALLBACK vlc_cancel_self (ULONG_PTR self)
689 struct vlc_thread *th = (void *)self;
691 if (likely(th != NULL))
695 void vlc_cancel (vlc_thread_t th)
698 QueueUserAPC (vlc_cancel_self, th->id, (uintptr_t)th);
700 SetEvent (th->cancel_event);
704 int vlc_savecancel (void)
706 struct vlc_thread *th = vlc_threadvar_get (thread_key);
708 return false; /* Main thread - cannot be cancelled anyway */
710 int state = th->killable;
711 th->killable = false;
715 void vlc_restorecancel (int state)
717 struct vlc_thread *th = vlc_threadvar_get (thread_key);
718 assert (state == false || state == true);
721 return; /* Main thread - cannot be cancelled anyway */
723 assert (!th->killable);
724 th->killable = state != 0;
727 void vlc_testcancel (void)
729 struct vlc_thread *th = vlc_threadvar_get (thread_key);
731 return; /* Main thread - cannot be cancelled anyway */
733 if (th->killable && th->killed)
735 for (vlc_cleanup_t *p = th->cleaners; p != NULL; p = p->next)
738 th->data = NULL; /* TODO: special value? */
739 vlc_thread_cleanup (th);
748 void vlc_control_cancel (int cmd, ...)
750 /* NOTE: This function only modifies thread-specific data, so there is no
751 * need to lock anything. */
754 struct vlc_thread *th = vlc_threadvar_get (thread_key);
756 return; /* Main thread - cannot be cancelled anyway */
761 case VLC_CLEANUP_PUSH:
763 /* cleaner is a pointer to the caller stack, no need to allocate
764 * and copy anything. As a nice side effect, this cannot fail. */
765 vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
766 cleaner->next = th->cleaners;
767 th->cleaners = cleaner;
771 case VLC_CLEANUP_POP:
773 th->cleaners = th->cleaners->next;
790 void (*func) (void *);
795 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
797 struct vlc_timer *timer = val;
800 timer->func (timer->data);
803 static void CALLBACK vlc_timer_do (unsigned timer_id, unsigned msg,
804 DWORD_PTR user, DWORD_PTR unused1,
807 struct vlc_timer *timer = (struct vlc_timer *) user;
808 assert (timer_id == timer->id);
813 timer->func (timer->data);
817 mtime_t interval = timer->interval * 1000;
818 vlc_timer_schedule (timer, false, interval, interval);
823 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
825 struct vlc_timer *timer = malloc (sizeof (*timer));
832 timer->handle = INVALID_HANDLE_VALUE;
841 void vlc_timer_destroy (vlc_timer_t timer)
844 if (timer->handle != INVALID_HANDLE_VALUE)
845 DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
848 timeKillEvent (timer->id);
849 /* FIXME: timers that have not yet completed will trigger use-after-free */
854 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
855 mtime_t value, mtime_t interval)
858 if (timer->handle != INVALID_HANDLE_VALUE)
860 DeleteTimerQueueTimer (NULL, timer->handle, NULL);
861 timer->handle = INVALID_HANDLE_VALUE;
866 timeKillEvent (timer->id);
876 value = (value + 999) / 1000;
877 interval = (interval + 999) / 1000;
880 if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
881 value, interval, WT_EXECUTEDEFAULT))
884 timeGetDevCaps (&caps, sizeof(caps));
886 unsigned delay = value;
887 delay = __MAX(delay, caps.wPeriodMin);
888 delay = __MIN(delay, caps.wPeriodMax);
890 unsigned event = TIME_ONESHOT;
892 if (interval == delay)
893 event = TIME_PERIODIC;
895 timer->interval = interval;
897 timer->id = timeSetEvent (delay, delay / 20, vlc_timer_do, (DWORD) timer,
904 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
912 unsigned vlc_GetCPUCount (void)
918 if (GetProcessAffinityMask (GetCurrentProcess(), &process, &system))
919 return popcount (system);