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>
38 static vlc_threadvar_t cancel_key;
41 * Per-thread cancellation data
43 typedef struct vlc_cancel_t
45 vlc_cleanup_t *cleaners;
54 # define VLC_CANCEL_INIT { NULL, true, false }
56 # define VLC_CANCEL_INIT { NULL, NULL, true, false }
60 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
62 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
65 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
68 /* Main thread - cannot be cancelled anyway */
69 return WaitForMultipleObjects (count, handles, FALSE, delay);
71 HANDLE new_handles[count + 1];
72 memcpy(new_handles, handles, count * sizeof(HANDLE));
73 new_handles[count] = nfo->cancel_event;
74 DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
76 if (result == WAIT_OBJECT_0 + count)
78 vlc_cancel_self (NULL);
79 return WAIT_IO_COMPLETION;
87 DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
91 DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
92 return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
96 Sleep(dwMilliseconds);
101 DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
106 /* The MSDN documentation specifies different return codes,
107 * but in practice they are the same. We just check that it
109 #if WAIT_ABANDONED != WAIT_ABANDONED_0
110 # error Windows headers changed, code needs to be rewritten!
112 return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
116 return WaitForSingleObject (hHandle, dwMilliseconds);
120 DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
121 BOOL bWaitAll, DWORD dwMilliseconds,
126 /* We do not support the bWaitAll case */
128 return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
132 return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
138 static vlc_mutex_t super_mutex;
140 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
147 case DLL_PROCESS_ATTACH:
148 vlc_mutex_init (&super_mutex);
149 vlc_threadvar_create (&cancel_key, free);
152 case DLL_PROCESS_DETACH:
153 vlc_threadvar_delete( &cancel_key );
154 vlc_mutex_destroy (&super_mutex);
161 void vlc_mutex_init( vlc_mutex_t *p_mutex )
163 /* This creates a recursive mutex. This is OK as fast mutexes have
164 * no defined behavior in case of recursive locking. */
165 InitializeCriticalSection (&p_mutex->mutex);
166 p_mutex->initialized = 1;
169 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
171 InitializeCriticalSection( &p_mutex->mutex );
172 p_mutex->initialized = 1;
176 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
178 assert (InterlockedExchange (&p_mutex->initialized, -1) == 1);
179 DeleteCriticalSection (&p_mutex->mutex);
182 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
184 if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
185 { /* ^^ We could also lock super_mutex all the time... sluggish */
186 assert (p_mutex != &super_mutex); /* this one cannot be static */
188 vlc_mutex_lock (&super_mutex);
189 if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
190 vlc_mutex_init (p_mutex);
191 /* FIXME: destroy the mutex some time... */
192 vlc_mutex_unlock (&super_mutex);
194 assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
195 EnterCriticalSection (&p_mutex->mutex);
198 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
200 if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
201 { /* ^^ We could also lock super_mutex all the time... sluggish */
202 assert (p_mutex != &super_mutex); /* this one cannot be static */
204 vlc_mutex_lock (&super_mutex);
205 if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
206 vlc_mutex_init (p_mutex);
207 /* FIXME: destroy the mutex some time... */
208 vlc_mutex_unlock (&super_mutex);
210 assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
211 return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
214 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
216 assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
217 LeaveCriticalSection (&p_mutex->mutex);
220 /*** Condition variables ***/
221 void vlc_cond_init( vlc_cond_t *p_condvar )
223 /* Create a manual-reset event (manual reset is needed for broadcast). */
224 *p_condvar = CreateEvent (NULL, TRUE, FALSE, NULL);
229 void vlc_cond_destroy (vlc_cond_t *p_condvar)
231 CloseHandle (*p_condvar);
234 void vlc_cond_signal (vlc_cond_t *p_condvar)
236 /* NOTE: This will cause a broadcast, that is wrong.
237 * This will also wake up the next waiting thread if no threads are yet
238 * waiting, which is also wrong. However both of these issues are allowed
239 * by the provision for spurious wakeups. Better have too many wakeups
240 * than too few (= deadlocks). */
241 SetEvent (*p_condvar);
244 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
246 SetEvent (*p_condvar);
249 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
256 LeaveCriticalSection (&p_mutex->mutex);
257 result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE);
258 EnterCriticalSection (&p_mutex->mutex);
260 while (result == WAIT_IO_COMPLETION);
262 assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
263 assert (result != WAIT_FAILED);
264 ResetEvent (*p_condvar);
267 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
276 mtime_t total = (deadline - mdate ())/1000;
280 DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
281 LeaveCriticalSection (&p_mutex->mutex);
282 result = WaitForSingleObjectEx (*p_condvar, delay, TRUE);
283 EnterCriticalSection (&p_mutex->mutex);
285 while (result == WAIT_IO_COMPLETION);
287 assert (result != WAIT_ABANDONED);
288 assert (result != WAIT_FAILED);
289 ResetEvent (*p_condvar);
291 return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
294 /*** Thread-specific variables (TLS) ***/
295 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
297 #warning FIXME: use destr() callback and stop leaking!
299 return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
302 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
308 * Sets a thread-local variable.
309 * @param key thread-local variable key (created with vlc_threadvar_create())
310 * @param value new value for the variable for the calling thread
311 * @return 0 on success, a system error code otherwise.
313 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
315 return TlsSetValue (key, value) ? ENOMEM : 0;
319 * Gets the value of a thread-local variable for the calling thread.
320 * This function cannot fail.
321 * @return the value associated with the given variable for the calling
322 * or NULL if there is no value.
324 void *vlc_threadvar_get (vlc_threadvar_t key)
326 return TlsGetValue (key);
331 static unsigned __stdcall vlc_entry (void *data)
333 vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
334 vlc_thread_t self = data;
336 cancel_data.cancel_event = self->cancel_event;
339 vlc_threadvar_set (cancel_key, &cancel_data);
340 self->data = self->entry (self->data);
344 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
347 /* When using the MSVCRT C library you have to use the _beginthreadex
348 * function instead of CreateThread, otherwise you'll end up with
349 * memory leaks and the signal functions not working (see Microsoft
350 * Knowledge Base, article 104641) */
352 vlc_thread_t th = malloc (sizeof (*th));
359 #if defined( UNDER_CE )
360 th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
361 if (th->cancel_event == NULL)
366 hThread = CreateThread (NULL, 128*1024, vlc_entry, th, CREATE_SUSPENDED, NULL);
368 hThread = (HANDLE)(uintptr_t)
369 _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
375 /* Thread closes the handle when exiting, duplicate it here
376 * to be on the safe side when joining. */
377 if (!DuplicateHandle (GetCurrentProcess (), hThread,
378 GetCurrentProcess (), &th->handle, 0, FALSE,
379 DUPLICATE_SAME_ACCESS))
381 CloseHandle (hThread);
386 th->handle = hThread;
389 ResumeThread (hThread);
391 SetThreadPriority (hThread, priority);
400 void vlc_join (vlc_thread_t handle, void **result)
404 while (WaitForSingleObjectEx (handle->handle, INFINITE, TRUE)
405 == WAIT_IO_COMPLETION);
407 CloseHandle (handle->handle);
409 *result = handle->data;
411 CloseHandle (handle->cancel_event);
417 /*** Thread cancellation ***/
419 /* APC procedure for thread cancellation */
420 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
423 vlc_control_cancel (VLC_DO_CANCEL);
426 void vlc_cancel (vlc_thread_t thread_id)
429 QueueUserAPC (vlc_cancel_self, thread_id->handle, 0);
431 SetEvent (thread_id->cancel_event);
435 int vlc_savecancel (void)
439 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
441 return false; /* Main thread - cannot be cancelled anyway */
443 state = nfo->killable;
444 nfo->killable = false;
448 void vlc_restorecancel (int state)
450 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
451 assert (state == false || state == true);
454 return; /* Main thread - cannot be cancelled anyway */
456 assert (!nfo->killable);
457 nfo->killable = state != 0;
460 void vlc_testcancel (void)
462 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
464 return; /* Main thread - cannot be cancelled anyway */
466 if (nfo->killable && nfo->killed)
468 for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
478 void vlc_control_cancel (int cmd, ...)
480 /* NOTE: This function only modifies thread-specific data, so there is no
481 * need to lock anything. */
484 vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
486 return; /* Main thread - cannot be cancelled anyway */
495 case VLC_CLEANUP_PUSH:
497 /* cleaner is a pointer to the caller stack, no need to allocate
498 * and copy anything. As a nice side effect, this cannot fail. */
499 vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
500 cleaner->next = nfo->cleaners;
501 nfo->cleaners = cleaner;
505 case VLC_CLEANUP_POP:
507 nfo->cleaners = nfo->cleaners->next;
516 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
518 vlc_timer_t *id = val;
521 if (TryEnterCriticalSection (&id->serializer))
523 id->overrun = InterlockedExchange (&id->counter, 0);
524 id->func (id, id->data);
525 LeaveCriticalSection (&id->serializer);
528 InterlockedIncrement (&id->counter);
531 int vlc_timer_create (vlc_timer_t *id, void (*func) (vlc_timer_t *, void *),
537 id->handle = INVALID_HANDLE_VALUE;
538 InitializeCriticalSection (&id->serializer);
542 void vlc_timer_destroy (vlc_timer_t *id)
544 if (id->handle != INVALID_HANDLE_VALUE)
545 DeleteTimerQueueTimer (NULL, id->handle, NULL);
546 DeleteCriticalSection (&id->serializer);
549 void vlc_timer_schedule (vlc_timer_t *id, bool absolute,
550 mtime_t value, mtime_t interval)
552 if (id->handle != INVALID_HANDLE_VALUE)
554 DeleteTimerQueueTimer (NULL, id->handle, NULL);
555 id->handle = INVALID_HANDLE_VALUE;
562 value = (value + 999) / 1000;
563 interval = (interval + 999) / 1000;
564 if (!CreateTimerQueueTimer (&id->handle, NULL, vlc_timer_do, id, value,
565 interval, WT_EXECUTEDEFAULT))
569 unsigned vlc_timer_getoverrun (const vlc_timer_t *id)