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>
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 static vlc_mutex_t super_mutex
;
145 BOOL WINAPI
DllMain (HINSTANCE hinstDll
, DWORD fdwReason
, LPVOID lpvReserved
)
152 case DLL_PROCESS_ATTACH
:
153 vlc_mutex_init (&super_mutex
);
154 vlc_threadvar_create (&cancel_key
, free
);
157 case DLL_PROCESS_DETACH
:
158 vlc_threadvar_delete( &cancel_key
);
159 vlc_mutex_destroy (&super_mutex
);
166 void vlc_mutex_init( vlc_mutex_t
*p_mutex
)
168 /* This creates a recursive mutex. This is OK as fast mutexes have
169 * no defined behavior in case of recursive locking. */
170 InitializeCriticalSection (&p_mutex
->mutex
);
171 p_mutex
->initialized
= 1;
174 void vlc_mutex_init_recursive( vlc_mutex_t
*p_mutex
)
176 InitializeCriticalSection( &p_mutex
->mutex
);
177 p_mutex
->initialized
= 1;
181 void vlc_mutex_destroy (vlc_mutex_t
*p_mutex
)
183 assert (InterlockedExchange (&p_mutex
->initialized
, -1) == 1);
184 DeleteCriticalSection (&p_mutex
->mutex
);
187 void vlc_mutex_lock (vlc_mutex_t
*p_mutex
)
189 if (InterlockedCompareExchange (&p_mutex
->initialized
, 0, 0) == 0)
190 { /* ^^ We could also lock super_mutex all the time... sluggish */
191 assert (p_mutex
!= &super_mutex
); /* this one cannot be static */
193 vlc_mutex_lock (&super_mutex
);
194 if (InterlockedCompareExchange (&p_mutex
->initialized
, 0, 0) == 0)
195 vlc_mutex_init (p_mutex
);
196 /* FIXME: destroy the mutex some time... */
197 vlc_mutex_unlock (&super_mutex
);
199 assert (InterlockedExchange (&p_mutex
->initialized
, 1) == 1);
200 EnterCriticalSection (&p_mutex
->mutex
);
203 int vlc_mutex_trylock (vlc_mutex_t
*p_mutex
)
205 if (InterlockedCompareExchange (&p_mutex
->initialized
, 0, 0) == 0)
206 { /* ^^ We could also lock super_mutex all the time... sluggish */
207 assert (p_mutex
!= &super_mutex
); /* this one cannot be static */
209 vlc_mutex_lock (&super_mutex
);
210 if (InterlockedCompareExchange (&p_mutex
->initialized
, 0, 0) == 0)
211 vlc_mutex_init (p_mutex
);
212 /* FIXME: destroy the mutex some time... */
213 vlc_mutex_unlock (&super_mutex
);
215 assert (InterlockedExchange (&p_mutex
->initialized
, 1) == 1);
216 return TryEnterCriticalSection (&p_mutex
->mutex
) ? 0 : EBUSY
;
219 void vlc_mutex_unlock (vlc_mutex_t
*p_mutex
)
221 assert (InterlockedExchange (&p_mutex
->initialized
, 1) == 1);
222 LeaveCriticalSection (&p_mutex
->mutex
);
225 /*** Condition variables ***/
226 void vlc_cond_init( vlc_cond_t
*p_condvar
)
228 /* Create a manual-reset event (manual reset is needed for broadcast). */
229 *p_condvar
= CreateEvent (NULL
, TRUE
, FALSE
, NULL
);
234 void vlc_cond_destroy (vlc_cond_t
*p_condvar
)
236 CloseHandle (*p_condvar
);
239 void vlc_cond_signal (vlc_cond_t
*p_condvar
)
241 /* NOTE: This will cause a broadcast, that is wrong.
242 * This will also wake up the next waiting thread if no threads are yet
243 * waiting, which is also wrong. However both of these issues are allowed
244 * by the provision for spurious wakeups. Better have too many wakeups
245 * than too few (= deadlocks). */
246 SetEvent (*p_condvar
);
249 void vlc_cond_broadcast (vlc_cond_t
*p_condvar
)
251 SetEvent (*p_condvar
);
254 void vlc_cond_wait (vlc_cond_t
*p_condvar
, vlc_mutex_t
*p_mutex
)
261 LeaveCriticalSection (&p_mutex
->mutex
);
262 result
= WaitForSingleObjectEx (*p_condvar
, INFINITE
, TRUE
);
263 EnterCriticalSection (&p_mutex
->mutex
);
265 while (result
== WAIT_IO_COMPLETION
);
267 assert (result
!= WAIT_ABANDONED
); /* another thread failed to cleanup! */
268 assert (result
!= WAIT_FAILED
);
269 ResetEvent (*p_condvar
);
272 int vlc_cond_timedwait (vlc_cond_t
*p_condvar
, vlc_mutex_t
*p_mutex
,
281 mtime_t total
= (deadline
- mdate ())/1000;
285 DWORD delay
= (total
> 0x7fffffff) ? 0x7fffffff : total
;
286 LeaveCriticalSection (&p_mutex
->mutex
);
287 result
= WaitForSingleObjectEx (*p_condvar
, delay
, TRUE
);
288 EnterCriticalSection (&p_mutex
->mutex
);
290 while (result
== WAIT_IO_COMPLETION
);
292 assert (result
!= WAIT_ABANDONED
);
293 assert (result
!= WAIT_FAILED
);
294 ResetEvent (*p_condvar
);
296 return (result
== WAIT_OBJECT_0
) ? 0 : ETIMEDOUT
;
299 /*** Read/write locks */
300 /* SRW (Slim Read Write) locks are available in Vista+ only */
301 void vlc_rwlock_init (vlc_rwlock_t
*lock
)
303 vlc_mutex_init (&lock
->mutex
);
304 vlc_cond_init (&lock
->read_wait
);
305 vlc_cond_init (&lock
->write_wait
);
306 lock
->readers
= 0; /* active readers */
307 lock
->writers
= 0; /* waiting or active writers */
308 lock
->writer
= 0; /* ID of active writer */
312 * Destroys an initialized unused read/write lock.
314 void vlc_rwlock_destroy (vlc_rwlock_t
*lock
)
316 vlc_cond_destroy (&lock
->read_wait
);
317 vlc_cond_destroy (&lock
->write_wait
);
318 vlc_mutex_destroy (&lock
->mutex
);
322 * Acquires a read/write lock for reading. Recursion is allowed.
324 void vlc_rwlock_rdlock (vlc_rwlock_t
*lock
)
326 vlc_mutex_lock (&lock
->mutex
);
327 while (lock
->writer
!= 0)
328 vlc_cond_wait (&lock
->read_wait
, &lock
->mutex
);
329 if (lock
->readers
== ULONG_MAX
)
332 vlc_mutex_unlock (&lock
->mutex
);
336 * Acquires a read/write lock for writing. Recursion is not allowed.
338 void vlc_rwlock_wrlock (vlc_rwlock_t
*lock
)
340 vlc_mutex_lock (&lock
->mutex
);
341 if (lock
->writers
== ULONG_MAX
)
344 while ((lock
->readers
> 0) || (lock
->writer
!= 0))
345 vlc_cond_wait (&lock
->write_wait
, &lock
->mutex
);
347 lock
->writer
= GetCurrentThreadId ();
348 vlc_mutex_unlock (&lock
->mutex
);
352 * Releases a read/write lock.
354 void vlc_rwlock_unlock (vlc_rwlock_t
*lock
)
356 vlc_mutex_lock (&lock
->mutex
);
357 if (lock
->readers
> 0)
358 lock
->readers
--; /* Read unlock */
360 lock
->writer
= 0; /* Write unlock */
362 if (lock
->writers
> 0)
364 if (lock
->readers
== 0)
365 vlc_cond_signal (&lock
->write_wait
);
368 vlc_cond_broadcast (&lock
->read_wait
);
369 vlc_mutex_unlock (&lock
->mutex
);
372 /*** Thread-specific variables (TLS) ***/
373 int vlc_threadvar_create (vlc_threadvar_t
*p_tls
, void (*destr
) (void *))
375 #warning FIXME: use destr() callback and stop leaking!
379 return (*p_tls
== TLS_OUT_OF_INDEXES
) ? EAGAIN
: 0;
382 void vlc_threadvar_delete (vlc_threadvar_t
*p_tls
)
388 * Sets a thread-local variable.
389 * @param key thread-local variable key (created with vlc_threadvar_create())
390 * @param value new value for the variable for the calling thread
391 * @return 0 on success, a system error code otherwise.
393 int vlc_threadvar_set (vlc_threadvar_t key
, void *value
)
395 return TlsSetValue (key
, value
) ? ENOMEM
: 0;
399 * Gets the value of a thread-local variable for the calling thread.
400 * This function cannot fail.
401 * @return the value associated with the given variable for the calling
402 * or NULL if there is no value.
404 void *vlc_threadvar_get (vlc_threadvar_t key
)
406 return TlsGetValue (key
);
411 void vlc_threads_setup (libvlc_int_t
*p_libvlc
)
416 struct vlc_entry_data
418 void * (*func
) (void *);
425 static unsigned __stdcall
vlc_entry (void *p
)
427 vlc_cancel_t cancel_data
= VLC_CANCEL_INIT
;
428 struct vlc_entry_data data
;
430 memcpy (&data
, p
, sizeof (data
));
434 cancel_data
.cancel_event
= data
.cancel_event
;
437 vlc_threadvar_set (cancel_key
, &cancel_data
);
438 data
.func (data
.data
);
442 int vlc_clone (vlc_thread_t
*p_handle
, void * (*entry
) (void *), void *data
,
448 struct vlc_entry_data
*entry_data
= malloc (sizeof (*entry_data
));
449 if (entry_data
== NULL
)
451 entry_data
->func
= entry
;
452 entry_data
->data
= data
;
455 /* When using the MSVCRT C library you have to use the _beginthreadex
456 * function instead of CreateThread, otherwise you'll end up with
457 * memory leaks and the signal functions not working (see Microsoft
458 * Knowledge Base, article 104641) */
459 hThread
= (HANDLE
)(uintptr_t)
460 _beginthreadex (NULL
, 0, vlc_entry
, entry_data
, CREATE_SUSPENDED
, NULL
);
467 /* Thread closes the handle when exiting, duplicate it here
468 * to be on the safe side when joining. */
469 if (!DuplicateHandle (GetCurrentProcess (), hThread
,
470 GetCurrentProcess (), p_handle
, 0, FALSE
,
471 DUPLICATE_SAME_ACCESS
))
473 CloseHandle (hThread
);
478 vlc_thread_t th
= malloc (sizeof (*th
));
481 th
->cancel_event
= CreateEvent (NULL
, FALSE
, FALSE
, NULL
);
482 if (th
->cancel_event
== NULL
)
487 entry_data
->cancel_event
= th
->cancel_event
;
489 /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
490 * Thread handles act up, too. */
491 th
->handle
= CreateThread (NULL
, 128*1024, vlc_entry
, entry_data
,
492 CREATE_SUSPENDED
, NULL
);
493 if (th
->handle
== NULL
)
495 CloseHandle (th
->cancel_event
);
501 hThread
= th
->handle
;
505 ResumeThread (hThread
);
507 SetThreadPriority (hThread
, priority
);
516 void vlc_join (vlc_thread_t handle
, void **result
)
519 # define handle handle->handle
523 while (WaitForSingleObjectEx (handle
, INFINITE
, TRUE
)
524 == WAIT_IO_COMPLETION
);
526 CloseHandle (handle
);
527 assert (result
== NULL
); /* <- FIXME if ever needed */
530 CloseHandle (handle
->cancel_event
);
535 void vlc_detach (vlc_thread_t handle
)
538 CloseHandle (handle
);
540 /* FIXME: handle->cancel_event leak */
541 CloseHandle (handle
->handle
);
546 /*** Thread cancellation ***/
548 /* APC procedure for thread cancellation */
549 static void CALLBACK
vlc_cancel_self (ULONG_PTR dummy
)
552 vlc_control_cancel (VLC_DO_CANCEL
);
555 void vlc_cancel (vlc_thread_t thread_id
)
558 QueueUserAPC (vlc_cancel_self
, thread_id
, 0);
560 SetEvent (thread_id
->cancel_event
);
564 int vlc_savecancel (void)
568 vlc_cancel_t
*nfo
= vlc_threadvar_get (cancel_key
);
570 return false; /* Main thread - cannot be cancelled anyway */
572 state
= nfo
->killable
;
573 nfo
->killable
= false;
577 void vlc_restorecancel (int state
)
579 vlc_cancel_t
*nfo
= vlc_threadvar_get (cancel_key
);
580 assert (state
== false || state
== true);
583 return; /* Main thread - cannot be cancelled anyway */
585 assert (!nfo
->killable
);
586 nfo
->killable
= state
!= 0;
589 void vlc_testcancel (void)
591 vlc_cancel_t
*nfo
= vlc_threadvar_get (cancel_key
);
593 return; /* Main thread - cannot be cancelled anyway */
595 if (nfo
->killable
&& nfo
->killed
)
597 for (vlc_cleanup_t
*p
= nfo
->cleaners
; p
!= NULL
; p
= p
->next
)
607 void vlc_control_cancel (int cmd
, ...)
609 /* NOTE: This function only modifies thread-specific data, so there is no
610 * need to lock anything. */
613 vlc_cancel_t
*nfo
= vlc_threadvar_get (cancel_key
);
615 return; /* Main thread - cannot be cancelled anyway */
624 case VLC_CLEANUP_PUSH
:
626 /* cleaner is a pointer to the caller stack, no need to allocate
627 * and copy anything. As a nice side effect, this cannot fail. */
628 vlc_cleanup_t
*cleaner
= va_arg (ap
, vlc_cleanup_t
*);
629 cleaner
->next
= nfo
->cleaners
;
630 nfo
->cleaners
= cleaner
;
634 case VLC_CLEANUP_POP
:
636 nfo
->cleaners
= nfo
->cleaners
->next
;
653 void (*func
) (void *);
658 static void CALLBACK
vlc_timer_do (void *val
, BOOLEAN timeout
)
660 struct vlc_timer
*timer
= val
;
663 timer
->func (timer
->data
);
666 static void CALLBACK
vlc_timer_do (unsigned timer_id
, unsigned msg
,
667 DWORD_PTR user
, DWORD_PTR unused1
,
670 struct vlc_timer
*timer
= (struct vlc_timer
*) user
;
671 assert (timer_id
== timer
->id
);
676 timer
->func (timer
->data
);
680 mtime_t interval
= timer
->interval
* 1000;
681 vlc_timer_schedule (timer
, false, interval
, interval
);
686 int vlc_timer_create (vlc_timer_t
*id
, void (*func
) (void *), void *data
)
688 struct vlc_timer
*timer
= malloc (sizeof (*timer
));
695 timer
->handle
= INVALID_HANDLE_VALUE
;
704 void vlc_timer_destroy (vlc_timer_t timer
)
707 if (timer
->handle
!= INVALID_HANDLE_VALUE
)
708 DeleteTimerQueueTimer (NULL
, timer
->handle
, INVALID_HANDLE_VALUE
);
711 timeKillEvent (timer
->id
);
712 /* FIXME: timers that have not yet completed will trigger use-after-free */
717 void vlc_timer_schedule (vlc_timer_t timer
, bool absolute
,
718 mtime_t value
, mtime_t interval
)
721 if (timer
->handle
!= INVALID_HANDLE_VALUE
)
723 DeleteTimerQueueTimer (NULL
, timer
->handle
, NULL
);
724 timer
->handle
= INVALID_HANDLE_VALUE
;
729 timeKillEvent (timer
->id
);
739 value
= (value
+ 999) / 1000;
740 interval
= (interval
+ 999) / 1000;
743 if (!CreateTimerQueueTimer (&timer
->handle
, NULL
, vlc_timer_do
, timer
,
744 value
, interval
, WT_EXECUTEDEFAULT
))
747 timeGetDevCaps (&caps
, sizeof(caps
));
749 unsigned delay
= value
;
750 delay
= __MAX(delay
, caps
.wPeriodMin
);
751 delay
= __MIN(delay
, caps
.wPeriodMax
);
753 unsigned event
= TIME_ONESHOT
;
755 if (interval
== delay
)
756 event
= TIME_PERIODIC
;
758 timer
->interval
= interval
;
760 timer
->id
= timeSetEvent (delay
, delay
/ 20, vlc_timer_do
, (DWORD
) timer
,
767 unsigned vlc_timer_getoverrun (vlc_timer_t timer
)