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>
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>
39 static vlc_threadvar_t cancel_key;
42 * Per-thread cancellation data
44 typedef struct vlc_cancel_t
46 vlc_cleanup_t *cleaners;
55 # define VLC_CANCEL_INIT { NULL, true, false }
57 # define VLC_CANCEL_INIT { NULL, NULL, true, false }
61 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
63 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
66 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
69 /* Main thread - cannot be cancelled anyway */
70 return WaitForMultipleObjects (count, handles, FALSE, delay);
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,
77 if (result == WAIT_OBJECT_0 + count)
79 vlc_cancel_self (NULL);
80 return WAIT_IO_COMPLETION;
88 DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
92 DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
93 return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
97 Sleep(dwMilliseconds);
102 DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
107 /* The MSDN documentation specifies different return codes,
108 * but in practice they are the same. We just check that it
110 #if WAIT_ABANDONED != WAIT_ABANDONED_0
111 # error Windows headers changed, code needs to be rewritten!
113 return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
117 return WaitForSingleObject (hHandle, dwMilliseconds);
121 DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
122 BOOL bWaitAll, DWORD dwMilliseconds,
127 /* We do not support the bWaitAll case */
129 return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
133 return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
139 static vlc_mutex_t super_mutex;
141 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
148 case DLL_PROCESS_ATTACH:
149 vlc_mutex_init (&super_mutex);
150 vlc_threadvar_create (&cancel_key, free);
153 case DLL_PROCESS_DETACH:
154 vlc_threadvar_delete( &cancel_key );
155 vlc_mutex_destroy (&super_mutex);
162 void vlc_mutex_init( vlc_mutex_t *p_mutex )
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;
170 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
172 InitializeCriticalSection( &p_mutex->mutex );
173 p_mutex->initialized = 1;
177 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
179 assert (InterlockedExchange (&p_mutex->initialized, -1) == 1);
180 DeleteCriticalSection (&p_mutex->mutex);
183 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
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 */
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);
195 assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
196 EnterCriticalSection (&p_mutex->mutex);
199 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
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 */
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);
211 assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
212 return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
215 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
217 assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
218 LeaveCriticalSection (&p_mutex->mutex);
221 /*** Condition variables ***/
222 void vlc_cond_init( vlc_cond_t *p_condvar )
224 /* Create a manual-reset event (manual reset is needed for broadcast). */
225 *p_condvar = CreateEvent (NULL, TRUE, FALSE, NULL);
230 void vlc_cond_destroy (vlc_cond_t *p_condvar)
232 CloseHandle (*p_condvar);
235 void vlc_cond_signal (vlc_cond_t *p_condvar)
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);
245 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
247 SetEvent (*p_condvar);
250 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
257 LeaveCriticalSection (&p_mutex->mutex);
258 result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE);
259 EnterCriticalSection (&p_mutex->mutex);
261 while (result == WAIT_IO_COMPLETION);
263 assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
264 assert (result != WAIT_FAILED);
265 ResetEvent (*p_condvar);
268 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
277 mtime_t total = (deadline - mdate ())/1000;
281 DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
282 LeaveCriticalSection (&p_mutex->mutex);
283 result = WaitForSingleObjectEx (*p_condvar, delay, TRUE);
284 EnterCriticalSection (&p_mutex->mutex);
286 while (result == WAIT_IO_COMPLETION);
288 assert (result != WAIT_ABANDONED);
289 assert (result != WAIT_FAILED);
290 ResetEvent (*p_condvar);
292 return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
295 /*** Read/write locks */
296 /* SRW (Slim Read Write) locks are available in Vista+ only */
297 void vlc_rwlock_init (vlc_rwlock_t *lock)
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 */
308 * Destroys an initialized unused read/write lock.
310 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
312 vlc_cond_destroy (&lock->read_wait);
313 vlc_cond_destroy (&lock->write_wait);
314 vlc_mutex_destroy (&lock->mutex);
318 * Acquires a read/write lock for reading. Recursion is allowed.
320 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
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)
328 vlc_mutex_unlock (&lock->mutex);
332 * Acquires a read/write lock for writing. Recursion is not allowed.
334 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
336 vlc_mutex_lock (&lock->mutex);
337 if (lock->writers == ULONG_MAX)
340 while ((lock->readers > 0) || (lock->writer != 0))
341 vlc_cond_wait (&lock->write_wait, &lock->mutex);
343 lock->writer = GetCurrentThreadId ();
344 vlc_mutex_unlock (&lock->mutex);
348 * Releases a read/write lock.
350 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
352 vlc_mutex_lock (&lock->mutex);
353 if (lock->readers > 0)
354 lock->readers--; /* Read unlock */
356 lock->writer = 0; /* Write unlock */
358 if (lock->writers > 0)
360 if (lock->readers == 0)
361 vlc_cond_signal (&lock->write_wait);
364 vlc_cond_broadcast (&lock->read_wait);
365 vlc_mutex_unlock (&lock->mutex);
368 /*** Thread-specific variables (TLS) ***/
369 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
371 #warning FIXME: use destr() callback and stop leaking!
373 return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
376 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
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.
387 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
389 return TlsSetValue (key, value) ? ENOMEM : 0;
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.
398 void *vlc_threadvar_get (vlc_threadvar_t key)
400 return TlsGetValue (key);
405 void vlc_threads_setup (libvlc_int_t *p_libvlc)
410 struct vlc_entry_data
413 void * (*func) (void *);
417 static unsigned __stdcall vlc_entry (void *p)
419 vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
420 struct vlc_entry_data data;
422 memcpy (&data, p, sizeof (data));
426 cancel_data.cancel_event = data.handle->cancel_event;
429 vlc_threadvar_set (cancel_key, &cancel_data);
430 data.handle->result = data.func (data.data);
434 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
437 /* When using the MSVCRT C library you have to use the _beginthreadex
438 * function instead of CreateThread, otherwise you'll end up with
439 * memory leaks and the signal functions not working (see Microsoft
440 * Knowledge Base, article 104641) */
442 vlc_thread_t th = malloc (sizeof (*th));
447 struct vlc_entry_data *entry_data = malloc (sizeof (*entry_data));
448 if (entry_data == NULL)
453 entry_data->handle = th;
454 entry_data->func = entry;
455 entry_data->data = data;
457 #if defined( UNDER_CE )
458 th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
459 if (th->cancel_event == NULL)
465 hThread = CreateThread (NULL, 128*1024, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
467 hThread = (HANDLE)(uintptr_t)
468 _beginthreadex (NULL, 0, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
474 /* Thread closes the handle when exiting, duplicate it here
475 * to be on the safe side when joining. */
476 if (!DuplicateHandle (GetCurrentProcess (), hThread,
477 GetCurrentProcess (), &th->handle, 0, FALSE,
478 DUPLICATE_SAME_ACCESS))
480 CloseHandle (hThread);
486 th->handle = hThread;
489 ResumeThread (hThread);
491 SetThreadPriority (hThread, priority);
500 void vlc_join (vlc_thread_t handle, void **result)
504 while (WaitForSingleObjectEx (handle->handle, INFINITE, TRUE)
505 == WAIT_IO_COMPLETION);
507 CloseHandle (handle->handle);
509 *result = handle->result;
511 CloseHandle (handle->cancel_event);
517 /*** Thread cancellation ***/
519 /* APC procedure for thread cancellation */
520 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
523 vlc_control_cancel (VLC_DO_CANCEL);
526 void vlc_cancel (vlc_thread_t thread_id)
529 QueueUserAPC (vlc_cancel_self, thread_id->handle, 0);
531 SetEvent (thread_id->cancel_event);
535 int vlc_savecancel (void)
539 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
541 return false; /* Main thread - cannot be cancelled anyway */
543 state = nfo->killable;
544 nfo->killable = false;
548 void vlc_restorecancel (int state)
550 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
551 assert (state == false || state == true);
554 return; /* Main thread - cannot be cancelled anyway */
556 assert (!nfo->killable);
557 nfo->killable = state != 0;
560 void vlc_testcancel (void)
562 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
564 return; /* Main thread - cannot be cancelled anyway */
566 if (nfo->killable && nfo->killed)
568 for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
578 void vlc_control_cancel (int cmd, ...)
580 /* NOTE: This function only modifies thread-specific data, so there is no
581 * need to lock anything. */
584 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
586 return; /* Main thread - cannot be cancelled anyway */
595 case VLC_CLEANUP_PUSH:
597 /* cleaner is a pointer to the caller stack, no need to allocate
598 * and copy anything. As a nice side effect, this cannot fail. */
599 vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
600 cleaner->next = nfo->cleaners;
601 nfo->cleaners = cleaner;
605 case VLC_CLEANUP_POP:
607 nfo->cleaners = nfo->cleaners->next;
616 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
618 vlc_timer_t *id = val;
624 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
628 id->handle = INVALID_HANDLE_VALUE;
632 void vlc_timer_destroy (vlc_timer_t *id)
634 if (id->handle != INVALID_HANDLE_VALUE)
635 DeleteTimerQueueTimer (NULL, id->handle, INVALID_HANDLE_VALUE);
638 void vlc_timer_schedule (vlc_timer_t *id, bool absolute,
639 mtime_t value, mtime_t interval)
641 if (id->handle != INVALID_HANDLE_VALUE)
643 DeleteTimerQueueTimer (NULL, id->handle, NULL);
644 id->handle = INVALID_HANDLE_VALUE;
651 value = (value + 999) / 1000;
652 interval = (interval + 999) / 1000;
653 if (!CreateTimerQueueTimer (&id->handle, NULL, vlc_timer_do, id, value,
654 interval, WT_EXECUTEDEFAULT))
658 unsigned vlc_timer_getoverrun (const vlc_timer_t *id)