]> git.sesse.net Git - vlc/blob - src/misc/w32thread.c
Asynchronous timer API
[vlc] / src / misc / w32thread.c
1 /*****************************************************************************
2  * w32thread.c : Win32 back-end for LibVLC
3  *****************************************************************************
4  * Copyright (C) 1999-2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Jean-Marc Dressler <polux@via.ecp.fr>
8  *          Samuel Hocevar <sam@zoy.org>
9  *          Gildas Bazin <gbazin@netcourrier.com>
10  *          Clément Sténac
11  *          Rémi Denis-Courmont
12  *
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.
17  *
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.
22  *
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  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33
34 #include "libvlc.h"
35 #include <stdarg.h>
36 #include <assert.h>
37
38 static vlc_threadvar_t cancel_key;
39
40 /**
41  * Per-thread cancellation data
42  */
43 typedef struct vlc_cancel_t
44 {
45     vlc_cleanup_t *cleaners;
46 #ifdef UNDER_CE
47     HANDLE         cancel_event;
48 #endif
49     bool           killable;
50     bool           killed;
51 } vlc_cancel_t;
52
53 #ifndef UNDER_CE
54 # define VLC_CANCEL_INIT { NULL, true, false }
55 #else
56 # define VLC_CANCEL_INIT { NULL, NULL, true, false }
57 #endif
58
59 #ifdef UNDER_CE
60 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
61
62 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
63                                   DWORD delay)
64 {
65     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
66     if (nfo == NULL)
67     {
68         /* Main thread - cannot be cancelled anyway */
69         return WaitForMultipleObjects (count, handles, FALSE, delay);
70     }
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,
75                                            delay);
76     if (result == WAIT_OBJECT_0 + count)
77     {
78         vlc_cancel_self (NULL);
79         return WAIT_IO_COMPLETION;
80     }
81     else
82     {
83         return result;
84     }
85 }
86
87 DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
88 {
89     if (bAlertable)
90     {
91         DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
92         return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
93     }
94     else
95     {
96         Sleep(dwMilliseconds);
97         return 0;
98     }
99 }
100
101 DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
102                              BOOL bAlertable)
103 {
104     if (bAlertable)
105     {
106         /* The MSDN documentation specifies different return codes,
107          * but in practice they are the same. We just check that it
108          * remains so. */
109 #if WAIT_ABANDONED != WAIT_ABANDONED_0
110 # error Windows headers changed, code needs to be rewritten!
111 #endif
112         return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
113     }
114     else
115     {
116         return WaitForSingleObject (hHandle, dwMilliseconds);
117     }
118 }
119
120 DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
121                                 BOOL bWaitAll, DWORD dwMilliseconds,
122                                 BOOL bAlertable)
123 {
124     if (bAlertable)
125     {
126         /* We do not support the bWaitAll case */
127         assert (! bWaitAll);
128         return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
129     }
130     else
131     {
132         return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
133                                        dwMilliseconds);
134     }
135 }
136 #endif
137
138 static vlc_mutex_t super_mutex;
139
140 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
141 {
142     (void) hinstDll;
143     (void) lpvReserved;
144
145     switch (fdwReason)
146     {
147         case DLL_PROCESS_ATTACH:
148             vlc_mutex_init (&super_mutex);
149             vlc_threadvar_create (&cancel_key, free);
150             break;
151
152         case DLL_PROCESS_DETACH:
153             vlc_threadvar_delete( &cancel_key );
154             vlc_mutex_destroy (&super_mutex);
155             break;
156     }
157     return TRUE;
158 }
159
160 /*** Mutexes ***/
161 void vlc_mutex_init( vlc_mutex_t *p_mutex )
162 {
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;
167 }
168
169 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
170 {
171     InitializeCriticalSection( &p_mutex->mutex );
172     p_mutex->initialized = 1;
173 }
174
175
176 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
177 {
178     assert (InterlockedExchange (&p_mutex->initialized, -1) == 1);
179     DeleteCriticalSection (&p_mutex->mutex);
180 }
181
182 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
183 {
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 */
187
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);
193     }
194     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
195     EnterCriticalSection (&p_mutex->mutex);
196 }
197
198 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
199 {
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 */
203
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);
209     }
210     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
211     return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
212 }
213
214 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
215 {
216     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
217     LeaveCriticalSection (&p_mutex->mutex);
218 }
219
220 /*** Condition variables ***/
221 void vlc_cond_init( vlc_cond_t *p_condvar )
222 {
223     /* Create a manual-reset event (manual reset is needed for broadcast). */
224     *p_condvar = CreateEvent (NULL, TRUE, FALSE, NULL);
225     if (!*p_condvar)
226         abort();
227 }
228
229 void vlc_cond_destroy (vlc_cond_t *p_condvar)
230 {
231     CloseHandle (*p_condvar);
232 }
233
234 void vlc_cond_signal (vlc_cond_t *p_condvar)
235 {
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);
242 }
243
244 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
245 {
246     SetEvent (*p_condvar);
247 }
248
249 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
250 {
251     DWORD result;
252
253     do
254     {
255         vlc_testcancel ();
256         LeaveCriticalSection (&p_mutex->mutex);
257         result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE);
258         EnterCriticalSection (&p_mutex->mutex);
259     }
260     while (result == WAIT_IO_COMPLETION);
261
262     assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
263     assert (result != WAIT_FAILED);
264     ResetEvent (*p_condvar);
265 }
266
267 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
268                         mtime_t deadline)
269 {
270     DWORD result;
271
272     do
273     {
274         vlc_testcancel ();
275
276         mtime_t total = (deadline - mdate ())/1000;
277         if( total < 0 )
278             total = 0;
279
280         DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
281         LeaveCriticalSection (&p_mutex->mutex);
282         result = WaitForSingleObjectEx (*p_condvar, delay, TRUE);
283         EnterCriticalSection (&p_mutex->mutex);
284     }
285     while (result == WAIT_IO_COMPLETION);
286
287     assert (result != WAIT_ABANDONED);
288     assert (result != WAIT_FAILED);
289     ResetEvent (*p_condvar);
290
291     return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
292 }
293
294 /*** Thread-specific variables (TLS) ***/
295 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
296 {
297 #warning FIXME: use destr() callback and stop leaking!
298     *p_tls = TlsAlloc();
299     return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
300 }
301
302 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
303 {
304     TlsFree (*p_tls);
305 }
306
307 /**
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.
312  */
313 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
314 {
315     return TlsSetValue (key, value) ? ENOMEM : 0;
316 }
317
318 /**
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.
323  */
324 void *vlc_threadvar_get (vlc_threadvar_t key)
325 {
326     return TlsGetValue (key);
327 }
328
329
330 /*** Threads ***/
331 static unsigned __stdcall vlc_entry (void *data)
332 {
333     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
334     vlc_thread_t self = data;
335 #ifdef UNDER_CE
336     cancel_data.cancel_event = self->cancel_event;
337 #endif
338
339     vlc_threadvar_set (cancel_key, &cancel_data);
340     self->data = self->entry (self->data);
341     return 0;
342 }
343
344 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
345                int priority)
346 {
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) */
351     HANDLE hThread;
352     vlc_thread_t th = malloc (sizeof (*th));
353
354     if (th == NULL)
355         return ENOMEM;
356
357     th->data = data;
358     th->entry = entry;
359 #if defined( UNDER_CE )
360     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
361     if (th->cancel_event == NULL)
362     {
363         free(th);
364         return errno;
365     }
366     hThread = CreateThread (NULL, 128*1024, vlc_entry, th, CREATE_SUSPENDED, NULL);
367 #else
368     hThread = (HANDLE)(uintptr_t)
369         _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
370 #endif
371
372     if (hThread)
373     {
374 #ifndef UNDER_CE
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))
380         {
381             CloseHandle (hThread);
382             free (th);
383             return ENOMEM;
384         }
385 #else
386         th->handle = hThread;
387 #endif
388
389         ResumeThread (hThread);
390         if (priority)
391             SetThreadPriority (hThread, priority);
392
393         *p_handle = th;
394         return 0;
395     }
396     free (th);
397     return errno;
398 }
399
400 void vlc_join (vlc_thread_t handle, void **result)
401 {
402     do
403         vlc_testcancel ();
404     while (WaitForSingleObjectEx (handle->handle, INFINITE, TRUE)
405                                                         == WAIT_IO_COMPLETION);
406
407     CloseHandle (handle->handle);
408     if (result)
409         *result = handle->data;
410 #ifdef UNDER_CE
411     CloseHandle (handle->cancel_event);
412 #endif
413     free (handle);
414 }
415
416
417 /*** Thread cancellation ***/
418
419 /* APC procedure for thread cancellation */
420 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
421 {
422     (void)dummy;
423     vlc_control_cancel (VLC_DO_CANCEL);
424 }
425
426 void vlc_cancel (vlc_thread_t thread_id)
427 {
428 #ifndef UNDER_CE
429     QueueUserAPC (vlc_cancel_self, thread_id->handle, 0);
430 #else
431     SetEvent (thread_id->cancel_event);
432 #endif
433 }
434
435 int vlc_savecancel (void)
436 {
437     int state;
438
439     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
440     if (nfo == NULL)
441         return false; /* Main thread - cannot be cancelled anyway */
442
443     state = nfo->killable;
444     nfo->killable = false;
445     return state;
446 }
447
448 void vlc_restorecancel (int state)
449 {
450     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
451     assert (state == false || state == true);
452
453     if (nfo == NULL)
454         return; /* Main thread - cannot be cancelled anyway */
455
456     assert (!nfo->killable);
457     nfo->killable = state != 0;
458 }
459
460 void vlc_testcancel (void)
461 {
462     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
463     if (nfo == NULL)
464         return; /* Main thread - cannot be cancelled anyway */
465
466     if (nfo->killable && nfo->killed)
467     {
468         for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
469              p->proc (p->data);
470 #ifndef UNDER_CE
471         _endthread ();
472 #else
473         ExitThread(0);
474 #endif
475     }
476 }
477
478 void vlc_control_cancel (int cmd, ...)
479 {
480     /* NOTE: This function only modifies thread-specific data, so there is no
481      * need to lock anything. */
482     va_list ap;
483
484     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
485     if (nfo == NULL)
486         return; /* Main thread - cannot be cancelled anyway */
487
488     va_start (ap, cmd);
489     switch (cmd)
490     {
491         case VLC_DO_CANCEL:
492             nfo->killed = true;
493             break;
494
495         case VLC_CLEANUP_PUSH:
496         {
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;
502             break;
503         }
504
505         case VLC_CLEANUP_POP:
506         {
507             nfo->cleaners = nfo->cleaners->next;
508             break;
509         }
510     }
511     va_end (ap);
512 }
513
514
515 /*** Timers ***/
516 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
517 {
518     vlc_timer_t *id = val;
519
520     assert (timeout);
521     if (TryEnterCriticalSection (&id->serializer))
522     {
523         id->overrun = InterlockedExchange (&id->counter, 0);
524         id->func (id, id->data);
525         LeaveCriticalSection (&id->serializer);
526     }
527     else /* Overrun */
528         InterlockedIncrement (&id->counter);
529 }
530
531 int vlc_timer_create (vlc_timer_t *id, void (*func) (vlc_timer_t *, void *),
532                       void *data)
533 {
534     id->func = func;
535     id->data = data;
536     id->overrun = 0;
537     id->handle = INVALID_HANDLE_VALUE;
538     InitializeCriticalSection (&id->serializer);
539     return 0;
540 }
541
542 void vlc_timer_destroy (vlc_timer_t *id)
543 {
544     if (id->handle != INVALID_HANDLE_VALUE)
545         DeleteTimerQueueTimer (NULL, id->handle, NULL);
546     DeleteCriticalSection (&id->serializer);
547 }
548
549 void vlc_timer_schedule (vlc_timer_t *id, bool absolute,
550                          mtime_t value, mtime_t interval)
551 {
552     if (id->handle != INVALID_HANDLE_VALUE)
553     {
554         DeleteTimerQueueTimer (NULL, id->handle, NULL);
555         id->handle = INVALID_HANDLE_VALUE;
556     }
557     if (value == 0)
558         return; /* Disarm */
559
560     if (absolute)
561         value -= mdate ();
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))
566         abort ();
567 }
568
569 unsigned vlc_timer_getoverrun (const vlc_timer_t *id)
570 {
571     return id->overrun;
572 }