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 cancel_key;
46 * Per-thread cancellation data
48 typedef struct vlc_cancel_t
50 vlc_cleanup_t *cleaners;
59 # define VLC_CANCEL_INIT { NULL, true, false }
61 # define VLC_CANCEL_INIT { NULL, NULL, true, false }
65 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
67 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
70 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_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] = nfo->cancel_event;
79 DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
81 if (result == WAIT_OBJECT_0 + count)
83 vlc_cancel_self (NULL);
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 (&cancel_key, free);
159 case DLL_PROCESS_DETACH:
160 vlc_threadvar_delete( &cancel_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 assert (p_mutex != &super_mutex); /* this one cannot be static */
196 vlc_mutex_lock (&super_mutex);
197 while (p_mutex->locked)
199 p_mutex->contention++;
200 vlc_cond_wait (&super_variable, &super_mutex);
201 p_mutex->contention--;
203 p_mutex->locked = true;
204 vlc_mutex_unlock (&super_mutex);
208 EnterCriticalSection (&p_mutex->mutex);
211 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
213 if (!p_mutex->dynamic)
214 { /* static mutexes */
217 assert (p_mutex != &super_mutex); /* this one cannot be static */
218 vlc_mutex_lock (&super_mutex);
219 if (!p_mutex->locked)
221 p_mutex->locked = true;
224 vlc_mutex_unlock (&super_mutex);
228 return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
231 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
233 if (!p_mutex->dynamic)
234 { /* static mutexes */
235 assert (p_mutex != &super_mutex); /* this one cannot be static */
237 vlc_mutex_lock (&super_mutex);
238 assert (p_mutex->locked);
239 p_mutex->locked = false;
240 if (p_mutex->contention)
241 vlc_cond_broadcast (&super_variable);
242 vlc_mutex_unlock (&super_mutex);
246 LeaveCriticalSection (&p_mutex->mutex);
249 /*** Condition variables ***/
256 static void vlc_cond_init_common (vlc_cond_t *p_condvar, unsigned clock)
258 /* Create a manual-reset event (manual reset is needed for broadcast). */
259 p_condvar->handle = CreateEvent (NULL, TRUE, FALSE, NULL);
260 if (!p_condvar->handle)
262 p_condvar->clock = clock;
265 void vlc_cond_init (vlc_cond_t *p_condvar)
267 vlc_cond_init_common (p_condvar, CLOCK_MONOTONIC);
270 void vlc_cond_init_daytime (vlc_cond_t *p_condvar)
272 vlc_cond_init_common (p_condvar, CLOCK_REALTIME);
275 void vlc_cond_destroy (vlc_cond_t *p_condvar)
277 CloseHandle (p_condvar->handle);
280 void vlc_cond_signal (vlc_cond_t *p_condvar)
282 /* NOTE: This will cause a broadcast, that is wrong.
283 * This will also wake up the next waiting thread if no threads are yet
284 * waiting, which is also wrong. However both of these issues are allowed
285 * by the provision for spurious wakeups. Better have too many wakeups
286 * than too few (= deadlocks). */
287 SetEvent (p_condvar->handle);
290 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
292 SetEvent (p_condvar->handle);
295 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
299 assert (p_mutex->dynamic); /* TODO */
303 LeaveCriticalSection (&p_mutex->mutex);
304 result = WaitForSingleObjectEx (p_condvar->handle, INFINITE, TRUE);
305 EnterCriticalSection (&p_mutex->mutex);
307 while (result == WAIT_IO_COMPLETION);
309 assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
310 assert (result != WAIT_FAILED);
311 ResetEvent (p_condvar->handle);
314 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
319 assert (p_mutex->dynamic); /* TODO */
325 switch (p_condvar->clock)
327 case CLOCK_MONOTONIC:
330 case CLOCK_REALTIME: /* FIXME? sub-second precision */
331 total = CLOCK_FREQ * time (NULL);
336 total = (deadline - total) / 1000;
340 DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
341 LeaveCriticalSection (&p_mutex->mutex);
342 result = WaitForSingleObjectEx (p_condvar->handle, delay, TRUE);
343 EnterCriticalSection (&p_mutex->mutex);
345 while (result == WAIT_IO_COMPLETION);
347 assert (result != WAIT_ABANDONED);
348 assert (result != WAIT_FAILED);
349 ResetEvent (p_condvar->handle);
351 return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
355 void vlc_sem_init (vlc_sem_t *sem, unsigned value)
357 *sem = CreateSemaphore (NULL, value, 0x7fffffff, NULL);
362 void vlc_sem_destroy (vlc_sem_t *sem)
367 int vlc_sem_post (vlc_sem_t *sem)
369 ReleaseSemaphore (*sem, 1, NULL);
370 return 0; /* FIXME */
373 void vlc_sem_wait (vlc_sem_t *sem)
380 result = WaitForSingleObjectEx (*sem, INFINITE, TRUE);
382 while (result == WAIT_IO_COMPLETION);
385 /*** Read/write locks */
386 /* SRW (Slim Read Write) locks are available in Vista+ only */
387 void vlc_rwlock_init (vlc_rwlock_t *lock)
389 vlc_mutex_init (&lock->mutex);
390 vlc_cond_init (&lock->read_wait);
391 vlc_cond_init (&lock->write_wait);
392 lock->readers = 0; /* active readers */
393 lock->writers = 0; /* waiting or active writers */
394 lock->writer = 0; /* ID of active writer */
398 * Destroys an initialized unused read/write lock.
400 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
402 vlc_cond_destroy (&lock->read_wait);
403 vlc_cond_destroy (&lock->write_wait);
404 vlc_mutex_destroy (&lock->mutex);
408 * Acquires a read/write lock for reading. Recursion is allowed.
410 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
412 vlc_mutex_lock (&lock->mutex);
413 while (lock->writer != 0)
414 vlc_cond_wait (&lock->read_wait, &lock->mutex);
415 if (lock->readers == ULONG_MAX)
418 vlc_mutex_unlock (&lock->mutex);
422 * Acquires a read/write lock for writing. Recursion is not allowed.
424 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
426 vlc_mutex_lock (&lock->mutex);
427 if (lock->writers == ULONG_MAX)
430 while ((lock->readers > 0) || (lock->writer != 0))
431 vlc_cond_wait (&lock->write_wait, &lock->mutex);
433 lock->writer = GetCurrentThreadId ();
434 vlc_mutex_unlock (&lock->mutex);
438 * Releases a read/write lock.
440 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
442 vlc_mutex_lock (&lock->mutex);
443 if (lock->readers > 0)
444 lock->readers--; /* Read unlock */
446 lock->writer = 0; /* Write unlock */
448 if (lock->writers > 0)
450 if (lock->readers == 0)
451 vlc_cond_signal (&lock->write_wait);
454 vlc_cond_broadcast (&lock->read_wait);
455 vlc_mutex_unlock (&lock->mutex);
458 /*** Thread-specific variables (TLS) ***/
459 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
461 #warning FIXME: use destr() callback and stop leaking!
465 return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
468 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
474 * Sets a thread-local variable.
475 * @param key thread-local variable key (created with vlc_threadvar_create())
476 * @param value new value for the variable for the calling thread
477 * @return 0 on success, a system error code otherwise.
479 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
481 return TlsSetValue (key, value) ? ENOMEM : 0;
485 * Gets the value of a thread-local variable for the calling thread.
486 * This function cannot fail.
487 * @return the value associated with the given variable for the calling
488 * or NULL if there is no value.
490 void *vlc_threadvar_get (vlc_threadvar_t key)
492 return TlsGetValue (key);
497 void vlc_threads_setup (libvlc_int_t *p_libvlc)
502 struct vlc_entry_data
504 void * (*func) (void *);
511 static unsigned __stdcall vlc_entry (void *p)
513 vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
514 struct vlc_entry_data data;
516 memcpy (&data, p, sizeof (data));
520 cancel_data.cancel_event = data.cancel_event;
523 vlc_threadvar_set (cancel_key, &cancel_data);
524 data.func (data.data);
528 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
534 struct vlc_entry_data *entry_data = malloc (sizeof (*entry_data));
535 if (entry_data == NULL)
537 entry_data->func = entry;
538 entry_data->data = data;
541 /* When using the MSVCRT C library you have to use the _beginthreadex
542 * function instead of CreateThread, otherwise you'll end up with
543 * memory leaks and the signal functions not working (see Microsoft
544 * Knowledge Base, article 104641) */
545 hThread = (HANDLE)(uintptr_t)
546 _beginthreadex (NULL, 0, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
553 /* Thread closes the handle when exiting, duplicate it here
554 * to be on the safe side when joining. */
555 if (!DuplicateHandle (GetCurrentProcess (), hThread,
556 GetCurrentProcess (), p_handle, 0, FALSE,
557 DUPLICATE_SAME_ACCESS))
559 CloseHandle (hThread);
564 vlc_thread_t th = malloc (sizeof (*th));
567 th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
568 if (th->cancel_event == NULL)
573 entry_data->cancel_event = th->cancel_event;
575 /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
576 * Thread handles act up, too. */
577 th->handle = CreateThread (NULL, 128*1024, vlc_entry, entry_data,
578 CREATE_SUSPENDED, NULL);
579 if (th->handle == NULL)
581 CloseHandle (th->cancel_event);
587 hThread = th->handle;
591 ResumeThread (hThread);
593 SetThreadPriority (hThread, priority);
602 void vlc_join (vlc_thread_t handle, void **result)
605 # define handle handle->handle
609 while (WaitForSingleObjectEx (handle, INFINITE, TRUE)
610 == WAIT_IO_COMPLETION);
612 CloseHandle (handle);
613 assert (result == NULL); /* <- FIXME if ever needed */
616 CloseHandle (handle->cancel_event);
621 void vlc_detach (vlc_thread_t handle)
624 CloseHandle (handle);
626 /* FIXME: handle->cancel_event leak */
627 CloseHandle (handle->handle);
632 /*** Thread cancellation ***/
634 /* APC procedure for thread cancellation */
635 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
638 vlc_control_cancel (VLC_DO_CANCEL);
641 void vlc_cancel (vlc_thread_t thread_id)
644 QueueUserAPC (vlc_cancel_self, thread_id, 0);
646 SetEvent (thread_id->cancel_event);
650 int vlc_savecancel (void)
654 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
656 return false; /* Main thread - cannot be cancelled anyway */
658 state = nfo->killable;
659 nfo->killable = false;
663 void vlc_restorecancel (int state)
665 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
666 assert (state == false || state == true);
669 return; /* Main thread - cannot be cancelled anyway */
671 assert (!nfo->killable);
672 nfo->killable = state != 0;
675 void vlc_testcancel (void)
677 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
679 return; /* Main thread - cannot be cancelled anyway */
681 if (nfo->killable && nfo->killed)
683 for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
693 void vlc_control_cancel (int cmd, ...)
695 /* NOTE: This function only modifies thread-specific data, so there is no
696 * need to lock anything. */
699 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
701 return; /* Main thread - cannot be cancelled anyway */
710 case VLC_CLEANUP_PUSH:
712 /* cleaner is a pointer to the caller stack, no need to allocate
713 * and copy anything. As a nice side effect, this cannot fail. */
714 vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
715 cleaner->next = nfo->cleaners;
716 nfo->cleaners = cleaner;
720 case VLC_CLEANUP_POP:
722 nfo->cleaners = nfo->cleaners->next;
739 void (*func) (void *);
744 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
746 struct vlc_timer *timer = val;
749 timer->func (timer->data);
752 static void CALLBACK vlc_timer_do (unsigned timer_id, unsigned msg,
753 DWORD_PTR user, DWORD_PTR unused1,
756 struct vlc_timer *timer = (struct vlc_timer *) user;
757 assert (timer_id == timer->id);
762 timer->func (timer->data);
766 mtime_t interval = timer->interval * 1000;
767 vlc_timer_schedule (timer, false, interval, interval);
772 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
774 struct vlc_timer *timer = malloc (sizeof (*timer));
781 timer->handle = INVALID_HANDLE_VALUE;
790 void vlc_timer_destroy (vlc_timer_t timer)
793 if (timer->handle != INVALID_HANDLE_VALUE)
794 DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
797 timeKillEvent (timer->id);
798 /* FIXME: timers that have not yet completed will trigger use-after-free */
803 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
804 mtime_t value, mtime_t interval)
807 if (timer->handle != INVALID_HANDLE_VALUE)
809 DeleteTimerQueueTimer (NULL, timer->handle, NULL);
810 timer->handle = INVALID_HANDLE_VALUE;
815 timeKillEvent (timer->id);
825 value = (value + 999) / 1000;
826 interval = (interval + 999) / 1000;
829 if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
830 value, interval, WT_EXECUTEDEFAULT))
833 timeGetDevCaps (&caps, sizeof(caps));
835 unsigned delay = value;
836 delay = __MAX(delay, caps.wPeriodMin);
837 delay = __MIN(delay, caps.wPeriodMax);
839 unsigned event = TIME_ONESHOT;
841 if (interval == delay)
842 event = TIME_PERIODIC;
844 timer->interval = interval;
846 timer->id = timeSetEvent (delay, delay / 20, vlc_timer_do, (DWORD) timer,
853 unsigned vlc_timer_getoverrun (vlc_timer_t timer)