1 /*****************************************************************************
2 * w32thread.c : Win32 back-end for LibVLC
3 *****************************************************************************
4 * Copyright (C) 1999-2009 the VideoLAN team
7 * Authors: Jean-Marc Dressler <polux@via.ecp.fr>
8 * Samuel Hocevar <sam@zoy.org>
9 * Gildas Bazin <gbazin@netcourrier.com>
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 *****************************************************************************/
33 #include <vlc_common.h>
41 # include <mmsystem.h>
44 static vlc_threadvar_t cancel_key;
47 * Per-thread cancellation data
49 typedef struct vlc_cancel_t
51 vlc_cleanup_t *cleaners;
60 # define VLC_CANCEL_INIT { NULL, true, false }
62 # define VLC_CANCEL_INIT { NULL, NULL, true, false }
66 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
68 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
71 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
74 /* Main thread - cannot be cancelled anyway */
75 return WaitForMultipleObjects (count, handles, FALSE, delay);
77 HANDLE new_handles[count + 1];
78 memcpy(new_handles, handles, count * sizeof(HANDLE));
79 new_handles[count] = nfo->cancel_event;
80 DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
82 if (result == WAIT_OBJECT_0 + count)
84 vlc_cancel_self (NULL);
85 return WAIT_IO_COMPLETION;
93 DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
97 DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
98 return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
102 Sleep(dwMilliseconds);
107 DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
112 /* The MSDN documentation specifies different return codes,
113 * but in practice they are the same. We just check that it
115 #if WAIT_ABANDONED != WAIT_ABANDONED_0
116 # error Windows headers changed, code needs to be rewritten!
118 return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
122 return WaitForSingleObject (hHandle, dwMilliseconds);
126 DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
127 BOOL bWaitAll, DWORD dwMilliseconds,
132 /* We do not support the bWaitAll case */
134 return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
138 return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
144 vlc_mutex_t super_mutex;
145 vlc_cond_t super_variable;
147 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
154 case DLL_PROCESS_ATTACH:
155 vlc_mutex_init (&super_mutex);
156 vlc_cond_init (&super_variable);
157 vlc_threadvar_create (&cancel_key, free);
160 case DLL_PROCESS_DETACH:
161 vlc_threadvar_delete( &cancel_key );
162 vlc_cond_destroy (&super_variable);
163 vlc_mutex_destroy (&super_mutex);
170 void vlc_mutex_init( vlc_mutex_t *p_mutex )
172 /* This creates a recursive mutex. This is OK as fast mutexes have
173 * no defined behavior in case of recursive locking. */
174 InitializeCriticalSection (&p_mutex->mutex);
175 p_mutex->dynamic = true;
178 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
180 InitializeCriticalSection( &p_mutex->mutex );
181 p_mutex->dynamic = true;
185 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
187 assert (p_mutex->dynamic);
188 DeleteCriticalSection (&p_mutex->mutex);
191 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
193 if (!p_mutex->dynamic)
194 { /* static mutexes */
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);
209 EnterCriticalSection (&p_mutex->mutex);
212 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
214 if (!p_mutex->dynamic)
215 { /* static mutexes */
218 assert (p_mutex != &super_mutex); /* this one cannot be static */
219 vlc_mutex_lock (&super_mutex);
220 if (!p_mutex->locked)
222 p_mutex->locked = true;
225 vlc_mutex_unlock (&super_mutex);
229 return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
232 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
234 if (!p_mutex->dynamic)
235 { /* static mutexes */
236 assert (p_mutex != &super_mutex); /* this one cannot be static */
238 vlc_mutex_lock (&super_mutex);
239 assert (p_mutex->locked);
240 p_mutex->locked = false;
241 if (p_mutex->contention)
242 vlc_cond_broadcast (&super_variable);
243 vlc_mutex_unlock (&super_mutex);
247 LeaveCriticalSection (&p_mutex->mutex);
250 /*** Condition variables ***/
251 void vlc_cond_init( vlc_cond_t *p_condvar )
253 /* Create a manual-reset event (manual reset is needed for broadcast). */
254 *p_condvar = CreateEvent (NULL, TRUE, FALSE, NULL);
259 void vlc_cond_destroy (vlc_cond_t *p_condvar)
261 CloseHandle (*p_condvar);
264 void vlc_cond_signal (vlc_cond_t *p_condvar)
266 /* NOTE: This will cause a broadcast, that is wrong.
267 * This will also wake up the next waiting thread if no threads are yet
268 * waiting, which is also wrong. However both of these issues are allowed
269 * by the provision for spurious wakeups. Better have too many wakeups
270 * than too few (= deadlocks). */
271 SetEvent (*p_condvar);
274 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
276 SetEvent (*p_condvar);
279 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
283 assert (p_mutex->dynamic); /* TODO */
287 LeaveCriticalSection (&p_mutex->mutex);
288 result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE);
289 EnterCriticalSection (&p_mutex->mutex);
291 while (result == WAIT_IO_COMPLETION);
293 assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
294 assert (result != WAIT_FAILED);
295 ResetEvent (*p_condvar);
298 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
303 assert (p_mutex->dynamic); /* TODO */
308 mtime_t total = (deadline - mdate ())/1000;
312 DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
313 LeaveCriticalSection (&p_mutex->mutex);
314 result = WaitForSingleObjectEx (*p_condvar, delay, TRUE);
315 EnterCriticalSection (&p_mutex->mutex);
317 while (result == WAIT_IO_COMPLETION);
319 assert (result != WAIT_ABANDONED);
320 assert (result != WAIT_FAILED);
321 ResetEvent (*p_condvar);
323 return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
327 void vlc_sem_init (vlc_sem_t *sem, unsigned value)
329 *sem = CreateSemaphore (NULL, value, 0x7fffffff, NULL);
334 void vlc_sem_destroy (vlc_sem_t *sem)
339 int vlc_sem_post (vlc_sem_t *sem)
341 ReleaseSemaphore (*sem, 1, NULL);
342 return 0; /* FIXME */
345 void vlc_sem_wait (vlc_sem_t *sem)
352 result = WaitForSingleObjectEx (*sem, INFINITE, TRUE);
354 while (result == WAIT_IO_COMPLETION);
357 /*** Read/write locks */
358 /* SRW (Slim Read Write) locks are available in Vista+ only */
359 void vlc_rwlock_init (vlc_rwlock_t *lock)
361 vlc_mutex_init (&lock->mutex);
362 vlc_cond_init (&lock->read_wait);
363 vlc_cond_init (&lock->write_wait);
364 lock->readers = 0; /* active readers */
365 lock->writers = 0; /* waiting or active writers */
366 lock->writer = 0; /* ID of active writer */
370 * Destroys an initialized unused read/write lock.
372 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
374 vlc_cond_destroy (&lock->read_wait);
375 vlc_cond_destroy (&lock->write_wait);
376 vlc_mutex_destroy (&lock->mutex);
380 * Acquires a read/write lock for reading. Recursion is allowed.
382 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
384 vlc_mutex_lock (&lock->mutex);
385 while (lock->writer != 0)
386 vlc_cond_wait (&lock->read_wait, &lock->mutex);
387 if (lock->readers == ULONG_MAX)
390 vlc_mutex_unlock (&lock->mutex);
394 * Acquires a read/write lock for writing. Recursion is not allowed.
396 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
398 vlc_mutex_lock (&lock->mutex);
399 if (lock->writers == ULONG_MAX)
402 while ((lock->readers > 0) || (lock->writer != 0))
403 vlc_cond_wait (&lock->write_wait, &lock->mutex);
405 lock->writer = GetCurrentThreadId ();
406 vlc_mutex_unlock (&lock->mutex);
410 * Releases a read/write lock.
412 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
414 vlc_mutex_lock (&lock->mutex);
415 if (lock->readers > 0)
416 lock->readers--; /* Read unlock */
418 lock->writer = 0; /* Write unlock */
420 if (lock->writers > 0)
422 if (lock->readers == 0)
423 vlc_cond_signal (&lock->write_wait);
426 vlc_cond_broadcast (&lock->read_wait);
427 vlc_mutex_unlock (&lock->mutex);
430 /*** Thread-specific variables (TLS) ***/
431 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
433 #warning FIXME: use destr() callback and stop leaking!
437 return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
440 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
446 * Sets a thread-local variable.
447 * @param key thread-local variable key (created with vlc_threadvar_create())
448 * @param value new value for the variable for the calling thread
449 * @return 0 on success, a system error code otherwise.
451 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
453 return TlsSetValue (key, value) ? ENOMEM : 0;
457 * Gets the value of a thread-local variable for the calling thread.
458 * This function cannot fail.
459 * @return the value associated with the given variable for the calling
460 * or NULL if there is no value.
462 void *vlc_threadvar_get (vlc_threadvar_t key)
464 return TlsGetValue (key);
469 void vlc_threads_setup (libvlc_int_t *p_libvlc)
474 struct vlc_entry_data
476 void * (*func) (void *);
483 static unsigned __stdcall vlc_entry (void *p)
485 vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
486 struct vlc_entry_data data;
488 memcpy (&data, p, sizeof (data));
492 cancel_data.cancel_event = data.cancel_event;
495 vlc_threadvar_set (cancel_key, &cancel_data);
496 data.func (data.data);
500 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
506 struct vlc_entry_data *entry_data = malloc (sizeof (*entry_data));
507 if (entry_data == NULL)
509 entry_data->func = entry;
510 entry_data->data = data;
513 /* When using the MSVCRT C library you have to use the _beginthreadex
514 * function instead of CreateThread, otherwise you'll end up with
515 * memory leaks and the signal functions not working (see Microsoft
516 * Knowledge Base, article 104641) */
517 hThread = (HANDLE)(uintptr_t)
518 _beginthreadex (NULL, 0, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
525 /* Thread closes the handle when exiting, duplicate it here
526 * to be on the safe side when joining. */
527 if (!DuplicateHandle (GetCurrentProcess (), hThread,
528 GetCurrentProcess (), p_handle, 0, FALSE,
529 DUPLICATE_SAME_ACCESS))
531 CloseHandle (hThread);
536 vlc_thread_t th = malloc (sizeof (*th));
539 th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
540 if (th->cancel_event == NULL)
545 entry_data->cancel_event = th->cancel_event;
547 /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
548 * Thread handles act up, too. */
549 th->handle = CreateThread (NULL, 128*1024, vlc_entry, entry_data,
550 CREATE_SUSPENDED, NULL);
551 if (th->handle == NULL)
553 CloseHandle (th->cancel_event);
559 hThread = th->handle;
563 ResumeThread (hThread);
565 SetThreadPriority (hThread, priority);
574 void vlc_join (vlc_thread_t handle, void **result)
577 # define handle handle->handle
581 while (WaitForSingleObjectEx (handle, INFINITE, TRUE)
582 == WAIT_IO_COMPLETION);
584 CloseHandle (handle);
585 assert (result == NULL); /* <- FIXME if ever needed */
588 CloseHandle (handle->cancel_event);
593 void vlc_detach (vlc_thread_t handle)
596 CloseHandle (handle);
598 /* FIXME: handle->cancel_event leak */
599 CloseHandle (handle->handle);
604 /*** Thread cancellation ***/
606 /* APC procedure for thread cancellation */
607 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
610 vlc_control_cancel (VLC_DO_CANCEL);
613 void vlc_cancel (vlc_thread_t thread_id)
616 QueueUserAPC (vlc_cancel_self, thread_id, 0);
618 SetEvent (thread_id->cancel_event);
622 int vlc_savecancel (void)
626 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
628 return false; /* Main thread - cannot be cancelled anyway */
630 state = nfo->killable;
631 nfo->killable = false;
635 void vlc_restorecancel (int state)
637 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
638 assert (state == false || state == true);
641 return; /* Main thread - cannot be cancelled anyway */
643 assert (!nfo->killable);
644 nfo->killable = state != 0;
647 void vlc_testcancel (void)
649 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
651 return; /* Main thread - cannot be cancelled anyway */
653 if (nfo->killable && nfo->killed)
655 for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
665 void vlc_control_cancel (int cmd, ...)
667 /* NOTE: This function only modifies thread-specific data, so there is no
668 * need to lock anything. */
671 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
673 return; /* Main thread - cannot be cancelled anyway */
682 case VLC_CLEANUP_PUSH:
684 /* cleaner is a pointer to the caller stack, no need to allocate
685 * and copy anything. As a nice side effect, this cannot fail. */
686 vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
687 cleaner->next = nfo->cleaners;
688 nfo->cleaners = cleaner;
692 case VLC_CLEANUP_POP:
694 nfo->cleaners = nfo->cleaners->next;
711 void (*func) (void *);
716 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
718 struct vlc_timer *timer = val;
721 timer->func (timer->data);
724 static void CALLBACK vlc_timer_do (unsigned timer_id, unsigned msg,
725 DWORD_PTR user, DWORD_PTR unused1,
728 struct vlc_timer *timer = (struct vlc_timer *) user;
729 assert (timer_id == timer->id);
734 timer->func (timer->data);
738 mtime_t interval = timer->interval * 1000;
739 vlc_timer_schedule (timer, false, interval, interval);
744 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
746 struct vlc_timer *timer = malloc (sizeof (*timer));
753 timer->handle = INVALID_HANDLE_VALUE;
762 void vlc_timer_destroy (vlc_timer_t timer)
765 if (timer->handle != INVALID_HANDLE_VALUE)
766 DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
769 timeKillEvent (timer->id);
770 /* FIXME: timers that have not yet completed will trigger use-after-free */
775 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
776 mtime_t value, mtime_t interval)
779 if (timer->handle != INVALID_HANDLE_VALUE)
781 DeleteTimerQueueTimer (NULL, timer->handle, NULL);
782 timer->handle = INVALID_HANDLE_VALUE;
787 timeKillEvent (timer->id);
797 value = (value + 999) / 1000;
798 interval = (interval + 999) / 1000;
801 if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
802 value, interval, WT_EXECUTEDEFAULT))
805 timeGetDevCaps (&caps, sizeof(caps));
807 unsigned delay = value;
808 delay = __MAX(delay, caps.wPeriodMin);
809 delay = __MIN(delay, caps.wPeriodMax);
811 unsigned event = TIME_ONESHOT;
813 if (interval == delay)
814 event = TIME_PERIODIC;
816 timer->interval = interval;
818 timer->id = timeSetEvent (delay, delay / 20, vlc_timer_do, (DWORD) timer,
825 unsigned vlc_timer_getoverrun (vlc_timer_t timer)