]> git.sesse.net Git - vlc/blob - src/win32/thread.c
Split mdate(), mwait(), msleep() to POSIX and Win32 files
[vlc] / src / win32 / thread.c
1 /*****************************************************************************
2  * thread.c : Win32 back-end for LibVLC
3  *****************************************************************************
4  * Copyright (C) 1999-2009 the VideoLAN team
5  *
6  * Authors: Jean-Marc Dressler <polux@via.ecp.fr>
7  *          Samuel Hocevar <sam@zoy.org>
8  *          Gildas Bazin <gbazin@netcourrier.com>
9  *          Clément Sténac
10  *          Rémi Denis-Courmont
11  *          Pierre Ynard
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 #include <limits.h>
38 #include <errno.h>
39 #ifdef UNDER_CE
40 # include <mmsystem.h>
41 #endif
42
43 static vlc_threadvar_t thread_key;
44
45 /**
46  * Per-thread data
47  */
48 struct vlc_thread
49 {
50     HANDLE         id;
51 #ifdef UNDER_CE
52     HANDLE         cancel_event;
53 #endif
54
55     bool           detached;
56     bool           killable;
57     bool           killed;
58     vlc_cleanup_t *cleaners;
59
60     void        *(*entry) (void *);
61     void          *data;
62 };
63
64 #ifdef UNDER_CE
65 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
66
67 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
68                                   DWORD delay)
69 {
70     struct vlc_thread *th = vlc_threadvar_get (thread_key);
71     if (th == NULL)
72     {
73         /* Main thread - cannot be cancelled anyway */
74         return WaitForMultipleObjects (count, handles, FALSE, delay);
75     }
76     HANDLE new_handles[count + 1];
77     memcpy(new_handles, handles, count * sizeof(HANDLE));
78     new_handles[count] = th->cancel_event;
79     DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
80                                            delay);
81     if (result == WAIT_OBJECT_0 + count)
82     {
83         vlc_cancel_self ((uintptr_t)th);
84         return WAIT_IO_COMPLETION;
85     }
86     else
87     {
88         return result;
89     }
90 }
91
92 DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
93 {
94     if (bAlertable)
95     {
96         DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
97         return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
98     }
99     else
100     {
101         Sleep(dwMilliseconds);
102         return 0;
103     }
104 }
105
106 DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
107                              BOOL bAlertable)
108 {
109     if (bAlertable)
110     {
111         /* The MSDN documentation specifies different return codes,
112          * but in practice they are the same. We just check that it
113          * remains so. */
114 #if WAIT_ABANDONED != WAIT_ABANDONED_0
115 # error Windows headers changed, code needs to be rewritten!
116 #endif
117         return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
118     }
119     else
120     {
121         return WaitForSingleObject (hHandle, dwMilliseconds);
122     }
123 }
124
125 DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
126                                 BOOL bWaitAll, DWORD dwMilliseconds,
127                                 BOOL bAlertable)
128 {
129     if (bAlertable)
130     {
131         /* We do not support the bWaitAll case */
132         assert (! bWaitAll);
133         return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
134     }
135     else
136     {
137         return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
138                                        dwMilliseconds);
139     }
140 }
141 #endif
142
143 vlc_mutex_t super_mutex;
144 vlc_cond_t  super_variable;
145
146 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
147 {
148     (void) hinstDll;
149     (void) lpvReserved;
150
151     switch (fdwReason)
152     {
153         case DLL_PROCESS_ATTACH:
154             vlc_mutex_init (&super_mutex);
155             vlc_cond_init (&super_variable);
156             vlc_threadvar_create (&thread_key, NULL);
157             break;
158
159         case DLL_PROCESS_DETACH:
160             vlc_threadvar_delete (&thread_key);
161             vlc_cond_destroy (&super_variable);
162             vlc_mutex_destroy (&super_mutex);
163             break;
164     }
165     return TRUE;
166 }
167
168 /*** Mutexes ***/
169 void vlc_mutex_init( vlc_mutex_t *p_mutex )
170 {
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;
175 }
176
177 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
178 {
179     InitializeCriticalSection( &p_mutex->mutex );
180     p_mutex->dynamic = true;
181 }
182
183
184 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
185 {
186     assert (p_mutex->dynamic);
187     DeleteCriticalSection (&p_mutex->mutex);
188 }
189
190 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
191 {
192     if (!p_mutex->dynamic)
193     {   /* static mutexes */
194         int canc = vlc_savecancel ();
195         assert (p_mutex != &super_mutex); /* this one cannot be static */
196
197         vlc_mutex_lock (&super_mutex);
198         while (p_mutex->locked)
199         {
200             p_mutex->contention++;
201             vlc_cond_wait (&super_variable, &super_mutex);
202             p_mutex->contention--;
203         }
204         p_mutex->locked = true;
205         vlc_mutex_unlock (&super_mutex);
206         vlc_restorecancel (canc);
207         return;
208     }
209
210     EnterCriticalSection (&p_mutex->mutex);
211 }
212
213 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
214 {
215     if (!p_mutex->dynamic)
216     {   /* static mutexes */
217         int ret = EBUSY;
218
219         assert (p_mutex != &super_mutex); /* this one cannot be static */
220         vlc_mutex_lock (&super_mutex);
221         if (!p_mutex->locked)
222         {
223             p_mutex->locked = true;
224             ret = 0;
225         }
226         vlc_mutex_unlock (&super_mutex);
227         return ret;
228     }
229
230     return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
231 }
232
233 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
234 {
235     if (!p_mutex->dynamic)
236     {   /* static mutexes */
237         assert (p_mutex != &super_mutex); /* this one cannot be static */
238
239         vlc_mutex_lock (&super_mutex);
240         assert (p_mutex->locked);
241         p_mutex->locked = false;
242         if (p_mutex->contention)
243             vlc_cond_broadcast (&super_variable);
244         vlc_mutex_unlock (&super_mutex);
245         return;
246     }
247
248     LeaveCriticalSection (&p_mutex->mutex);
249 }
250
251 /*** Condition variables ***/
252 enum
253 {
254     CLOCK_MONOTONIC,
255     CLOCK_REALTIME,
256 };
257
258 static void vlc_cond_init_common (vlc_cond_t *p_condvar, unsigned clock)
259 {
260     /* Create a manual-reset event (manual reset is needed for broadcast). */
261     p_condvar->handle = CreateEvent (NULL, TRUE, FALSE, NULL);
262     if (!p_condvar->handle)
263         abort();
264     p_condvar->clock = clock;
265 }
266
267 void vlc_cond_init (vlc_cond_t *p_condvar)
268 {
269     vlc_cond_init_common (p_condvar, CLOCK_MONOTONIC);
270 }
271
272 void vlc_cond_init_daytime (vlc_cond_t *p_condvar)
273 {
274     vlc_cond_init_common (p_condvar, CLOCK_REALTIME);
275 }
276
277 void vlc_cond_destroy (vlc_cond_t *p_condvar)
278 {
279     CloseHandle (p_condvar->handle);
280 }
281
282 void vlc_cond_signal (vlc_cond_t *p_condvar)
283 {
284     /* NOTE: This will cause a broadcast, that is wrong.
285      * This will also wake up the next waiting thread if no threads are yet
286      * waiting, which is also wrong. However both of these issues are allowed
287      * by the provision for spurious wakeups. Better have too many wakeups
288      * than too few (= deadlocks). */
289     SetEvent (p_condvar->handle);
290 }
291
292 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
293 {
294     SetEvent (p_condvar->handle);
295 }
296
297 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
298 {
299     DWORD result;
300
301     assert (p_mutex->dynamic); /* TODO */
302     do
303     {
304         vlc_testcancel ();
305         LeaveCriticalSection (&p_mutex->mutex);
306         result = WaitForSingleObjectEx (p_condvar->handle, INFINITE, TRUE);
307         EnterCriticalSection (&p_mutex->mutex);
308     }
309     while (result == WAIT_IO_COMPLETION);
310
311     assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
312     assert (result != WAIT_FAILED);
313     ResetEvent (p_condvar->handle);
314 }
315
316 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
317                         mtime_t deadline)
318 {
319     DWORD result;
320
321     assert (p_mutex->dynamic); /* TODO */
322     do
323     {
324         vlc_testcancel ();
325
326         mtime_t total;
327         switch (p_condvar->clock)
328         {
329             case CLOCK_MONOTONIC:
330                 total = mdate();
331                 break;
332             case CLOCK_REALTIME: /* FIXME? sub-second precision */
333                 total = CLOCK_FREQ * time (NULL);
334                 break;
335             default:
336                 assert (0);
337         }
338         total = (deadline - total) / 1000;
339         if( total < 0 )
340             total = 0;
341
342         DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
343         LeaveCriticalSection (&p_mutex->mutex);
344         result = WaitForSingleObjectEx (p_condvar->handle, delay, TRUE);
345         EnterCriticalSection (&p_mutex->mutex);
346     }
347     while (result == WAIT_IO_COMPLETION);
348
349     assert (result != WAIT_ABANDONED);
350     assert (result != WAIT_FAILED);
351     ResetEvent (p_condvar->handle);
352
353     return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
354 }
355
356 /*** Semaphore ***/
357 void vlc_sem_init (vlc_sem_t *sem, unsigned value)
358 {
359     *sem = CreateSemaphore (NULL, value, 0x7fffffff, NULL);
360     if (*sem == NULL)
361         abort ();
362 }
363
364 void vlc_sem_destroy (vlc_sem_t *sem)
365 {
366     CloseHandle (*sem);
367 }
368
369 int vlc_sem_post (vlc_sem_t *sem)
370 {
371     ReleaseSemaphore (*sem, 1, NULL);
372     return 0; /* FIXME */
373 }
374
375 void vlc_sem_wait (vlc_sem_t *sem)
376 {
377     DWORD result;
378
379     do
380     {
381         vlc_testcancel ();
382         result = WaitForSingleObjectEx (*sem, INFINITE, TRUE);
383     }
384     while (result == WAIT_IO_COMPLETION);
385 }
386
387 /*** Read/write locks */
388 /* SRW (Slim Read Write) locks are available in Vista+ only */
389 void vlc_rwlock_init (vlc_rwlock_t *lock)
390 {
391     vlc_mutex_init (&lock->mutex);
392     vlc_cond_init (&lock->read_wait);
393     vlc_cond_init (&lock->write_wait);
394     lock->readers = 0; /* active readers */
395     lock->writers = 0; /* waiting writers */
396     lock->writer = 0; /* ID of active writer */
397 }
398
399 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
400 {
401     vlc_cond_destroy (&lock->read_wait);
402     vlc_cond_destroy (&lock->write_wait);
403     vlc_mutex_destroy (&lock->mutex);
404 }
405
406 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
407 {
408     vlc_mutex_lock (&lock->mutex);
409     /* Recursive read-locking is allowed. With the infos available:
410      *  - the loosest possible condition (no active writer) is:
411      *     (lock->writer != 0)
412      *  - the strictest possible condition is:
413      *     (lock->writer != 0 || (lock->readers == 0 && lock->writers > 0))
414      *  or (lock->readers == 0 && (lock->writer != 0 || lock->writers > 0))
415      */
416     while (lock->writer != 0)
417     {
418         assert (lock->readers == 0);
419         vlc_cond_wait (&lock->read_wait, &lock->mutex);
420     }
421     if (unlikely(lock->readers == ULONG_MAX))
422         abort ();
423     lock->readers++;
424     vlc_mutex_unlock (&lock->mutex);
425 }
426
427 static void vlc_rwlock_rdunlock (vlc_rwlock_t *lock)
428 {
429     vlc_mutex_lock (&lock->mutex);
430     assert (lock->readers > 0);
431
432     /* If there are no readers left, wake up a writer. */
433     if (--lock->readers == 0 && lock->writers > 0)
434         vlc_cond_signal (&lock->write_wait);
435     vlc_mutex_unlock (&lock->mutex);
436 }
437
438 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
439 {
440     vlc_mutex_lock (&lock->mutex);
441     if (unlikely(lock->writers == ULONG_MAX))
442         abort ();
443     lock->writers++;
444     /* Wait until nobody owns the lock in either way. */
445     while ((lock->readers > 0) || (lock->writer != 0))
446         vlc_cond_wait (&lock->write_wait, &lock->mutex);
447     lock->writers--;
448     assert (lock->writer == 0);
449     lock->writer = GetCurrentThreadId ();
450     vlc_mutex_unlock (&lock->mutex);
451 }
452
453 static void vlc_rwlock_wrunlock (vlc_rwlock_t *lock)
454 {
455     vlc_mutex_lock (&lock->mutex);
456     assert (lock->writer == GetCurrentThreadId ());
457     assert (lock->readers == 0);
458     lock->writer = 0; /* Write unlock */
459
460     /* Let reader and writer compete. Scheduler decides who wins. */
461     if (lock->writers > 0)
462         vlc_cond_signal (&lock->write_wait);
463     vlc_cond_broadcast (&lock->read_wait);
464     vlc_mutex_unlock (&lock->mutex);
465 }
466
467 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
468 {
469     /* Note: If the lock is held for reading, lock->writer is nul.
470      * If the lock is held for writing, only this thread can store a value to
471      * lock->writer. Either way, lock->writer is safe to fetch here. */
472     if (lock->writer != 0)
473         vlc_rwlock_wrunlock (lock);
474     else
475         vlc_rwlock_rdunlock (lock);
476 }
477
478 /*** Thread-specific variables (TLS) ***/
479 struct vlc_threadvar
480 {
481     DWORD                 id;
482     void                (*destroy) (void *);
483     struct vlc_threadvar *prev;
484     struct vlc_threadvar *next;
485 } *vlc_threadvar_last = NULL;
486
487 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
488 {
489     struct vlc_threadvar *var = malloc (sizeof (*var));
490     if (unlikely(var == NULL))
491         return errno;
492
493     var->id = TlsAlloc();
494     if (var->id == TLS_OUT_OF_INDEXES)
495     {
496         free (var);
497         return EAGAIN;
498     }
499     var->destroy = destr;
500     var->next = NULL;
501     *p_tls = var;
502
503     vlc_mutex_lock (&super_mutex);
504     var->prev = vlc_threadvar_last;
505     vlc_threadvar_last = var;
506     vlc_mutex_unlock (&super_mutex);
507     return 0;
508 }
509
510 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
511 {
512     struct vlc_threadvar *var = *p_tls;
513
514     vlc_mutex_lock (&super_mutex);
515     if (var->prev != NULL)
516         var->prev->next = var->next;
517     else
518         vlc_threadvar_last = var->next;
519     if (var->next != NULL)
520         var->next->prev = var->prev;
521     vlc_mutex_unlock (&super_mutex);
522
523     TlsFree (var->id);
524     free (var);
525 }
526
527 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
528 {
529     return TlsSetValue (key->id, value) ? ENOMEM : 0;
530 }
531
532 void *vlc_threadvar_get (vlc_threadvar_t key)
533 {
534     return TlsGetValue (key->id);
535 }
536
537 /*** Threads ***/
538 void vlc_threads_setup (libvlc_int_t *p_libvlc)
539 {
540     (void) p_libvlc;
541 }
542
543 static void vlc_thread_cleanup (struct vlc_thread *th)
544 {
545     vlc_threadvar_t key;
546
547 retry:
548     /* TODO: use RW lock or something similar */
549     vlc_mutex_lock (&super_mutex);
550     for (key = vlc_threadvar_last; key != NULL; key = key->prev)
551     {
552         void *value = vlc_threadvar_get (key);
553         if (value != NULL && key->destroy != NULL)
554         {
555             vlc_mutex_unlock (&super_mutex);
556             vlc_threadvar_set (key, NULL);
557             key->destroy (value);
558             goto retry;
559         }
560     }
561     vlc_mutex_unlock (&super_mutex);
562
563     if (th->detached)
564     {
565         CloseHandle (th->id);
566 #ifdef UNDER_CE
567         CloseHandle (th->cancel_event);
568 #endif
569         free (th);
570     }
571 }
572
573 static unsigned __stdcall vlc_entry (void *p)
574 {
575     struct vlc_thread *th = p;
576
577     vlc_threadvar_set (thread_key, th);
578     th->killable = true;
579     th->data = th->entry (th->data);
580     vlc_thread_cleanup (th);
581     return 0;
582 }
583
584 static int vlc_clone_attr (vlc_thread_t *p_handle, bool detached,
585                            void *(*entry) (void *), void *data, int priority)
586 {
587     struct vlc_thread *th = malloc (sizeof (*th));
588     if (unlikely(th == NULL))
589         return ENOMEM;
590     th->entry = entry;
591     th->data = data;
592     th->detached = detached;
593     th->killable = false; /* not until vlc_entry() ! */
594     th->killed = false;
595     th->cleaners = NULL;
596
597     HANDLE hThread;
598 #ifndef UNDER_CE
599     /* When using the MSVCRT C library you have to use the _beginthreadex
600      * function instead of CreateThread, otherwise you'll end up with
601      * memory leaks and the signal functions not working (see Microsoft
602      * Knowledge Base, article 104641) */
603     hThread = (HANDLE)(uintptr_t)
604         _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
605     if (hThread == NULL)
606     {
607         int err = errno;
608         free (th);
609         return err;
610     }
611
612 #else
613     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
614     if (th->cancel_event == NULL)
615     {
616         free (th);
617         return ENOMEM;
618     }
619
620     /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
621      * Thread handles act up, too. */
622     hThread = CreateThread (NULL, 128*1024, vlc_entry, th,
623                             CREATE_SUSPENDED, NULL);
624     if (hThread == NULL)
625     {
626         CloseHandle (th->cancel_event);
627         free (th);
628         return ENOMEM;
629     }
630
631 #endif
632     /* Thread is suspended, so we can safely set th->id */
633     th->id = hThread;
634     if (p_handle != NULL)
635         *p_handle = th;
636
637     if (priority)
638         SetThreadPriority (hThread, priority);
639
640     ResumeThread (hThread);
641
642     return 0;
643 }
644
645 int vlc_clone (vlc_thread_t *p_handle, void *(*entry) (void *),
646                 void *data, int priority)
647 {
648     return vlc_clone_attr (p_handle, false, entry, data, priority);
649 }
650
651 void vlc_join (vlc_thread_t th, void **result)
652 {
653     do
654         vlc_testcancel ();
655     while (WaitForSingleObjectEx (th->id, INFINITE, TRUE)
656                                                         == WAIT_IO_COMPLETION);
657
658     if (result != NULL)
659         *result = th->data;
660     CloseHandle (th->id);
661 #ifdef UNDER_CE
662     CloseHandle (th->cancel_event);
663 #endif
664     free (th);
665 }
666
667 int vlc_clone_detach (vlc_thread_t *p_handle, void *(*entry) (void *),
668                       void *data, int priority)
669 {
670     vlc_thread_t th;
671     if (p_handle == NULL)
672         p_handle = &th;
673
674     return vlc_clone_attr (p_handle, true, entry, data, priority);
675 }
676
677 int vlc_set_priority (vlc_thread_t th, int priority)
678 {
679     if (!SetThreadPriority (th->id, priority))
680         return VLC_EGENERIC;
681     return VLC_SUCCESS;
682 }
683
684 /*** Thread cancellation ***/
685
686 /* APC procedure for thread cancellation */
687 static void CALLBACK vlc_cancel_self (ULONG_PTR self)
688 {
689     struct vlc_thread *th = (void *)self;
690
691     if (likely(th != NULL))
692         th->killed = true;
693 }
694
695 void vlc_cancel (vlc_thread_t th)
696 {
697 #ifndef UNDER_CE
698     QueueUserAPC (vlc_cancel_self, th->id, (uintptr_t)th);
699 #else
700     SetEvent (th->cancel_event);
701 #endif
702 }
703
704 int vlc_savecancel (void)
705 {
706     struct vlc_thread *th = vlc_threadvar_get (thread_key);
707     if (th == NULL)
708         return false; /* Main thread - cannot be cancelled anyway */
709
710     int state = th->killable;
711     th->killable = false;
712     return state;
713 }
714
715 void vlc_restorecancel (int state)
716 {
717     struct vlc_thread *th = vlc_threadvar_get (thread_key);
718     assert (state == false || state == true);
719
720     if (th == NULL)
721         return; /* Main thread - cannot be cancelled anyway */
722
723     assert (!th->killable);
724     th->killable = state != 0;
725 }
726
727 void vlc_testcancel (void)
728 {
729     struct vlc_thread *th = vlc_threadvar_get (thread_key);
730     if (th == NULL)
731         return; /* Main thread - cannot be cancelled anyway */
732
733     if (th->killable && th->killed)
734     {
735         for (vlc_cleanup_t *p = th->cleaners; p != NULL; p = p->next)
736              p->proc (p->data);
737
738         th->data = NULL; /* TODO: special value? */
739         vlc_thread_cleanup (th);
740 #ifndef UNDER_CE
741         _endthreadex(0);
742 #else
743         ExitThread(0);
744 #endif
745     }
746 }
747
748 void vlc_control_cancel (int cmd, ...)
749 {
750     /* NOTE: This function only modifies thread-specific data, so there is no
751      * need to lock anything. */
752     va_list ap;
753
754     struct vlc_thread *th = vlc_threadvar_get (thread_key);
755     if (th == NULL)
756         return; /* Main thread - cannot be cancelled anyway */
757
758     va_start (ap, cmd);
759     switch (cmd)
760     {
761         case VLC_CLEANUP_PUSH:
762         {
763             /* cleaner is a pointer to the caller stack, no need to allocate
764              * and copy anything. As a nice side effect, this cannot fail. */
765             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
766             cleaner->next = th->cleaners;
767             th->cleaners = cleaner;
768             break;
769         }
770
771         case VLC_CLEANUP_POP:
772         {
773             th->cleaners = th->cleaners->next;
774             break;
775         }
776     }
777     va_end (ap);
778 }
779
780
781 /*** Clock ***/
782 mtime_t mdate (void)
783 {
784     /* We don't need the real date, just the value of a high precision timer */
785     LARGE_INTEGER counter, freq;
786     if (!QueryPerformanceCounter (&counter)
787      || !QueryPerformanceFrequency (&freq))
788         abort();
789
790     /* Convert to from (1/freq) to microsecond resolution */
791     /* We need to split the division to avoid 63-bits overflow */
792     lldiv_t d = lldiv (counter.QuadPart, freq.QuadPart);
793
794     return (d.quot * 1000000) + ((d.rem * 1000000) / freq.QuadPart);
795 }
796
797 #undef mwait
798 void mwait (mtime_t deadline)
799 {
800     mtime_t delay;
801
802     vlc_testcancel();
803     while ((delay = (deadline - mdate())) > 0)
804     {
805         delay /= 1000;
806         if (unlikely(delay > 0x7fffffff))
807             delay = 0x7fffffff;
808         SleepEx (delay, TRUE);
809         vlc_testcancel();
810     }
811 }
812
813 #undef msleep
814 void msleep (mtime_t delay)
815 {
816     mwait (mdate () + delay);
817 }
818
819
820 /*** Timers ***/
821 struct vlc_timer
822 {
823 #ifndef UNDER_CE
824     HANDLE handle;
825 #else
826     unsigned id;
827     unsigned interval;
828 #endif
829     void (*func) (void *);
830     void *data;
831 };
832
833 #ifndef UNDER_CE
834 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
835 {
836     struct vlc_timer *timer = val;
837
838     assert (timeout);
839     timer->func (timer->data);
840 }
841 #else
842 static void CALLBACK vlc_timer_do (unsigned timer_id, unsigned msg,
843                                    DWORD_PTR user, DWORD_PTR unused1,
844                                    DWORD_PTR unused2)
845 {
846     struct vlc_timer *timer = (struct vlc_timer *) user;
847     assert (timer_id == timer->id);
848     (void) msg;
849     (void) unused1;
850     (void) unused2;
851
852     timer->func (timer->data);
853
854     if (timer->interval)
855     {
856         mtime_t interval = timer->interval * 1000;
857         vlc_timer_schedule (timer, false, interval, interval);
858     }
859 }
860 #endif
861
862 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
863 {
864     struct vlc_timer *timer = malloc (sizeof (*timer));
865
866     if (timer == NULL)
867         return ENOMEM;
868     timer->func = func;
869     timer->data = data;
870 #ifndef UNDER_CE
871     timer->handle = INVALID_HANDLE_VALUE;
872 #else
873     timer->id = 0;
874     timer->interval = 0;
875 #endif
876     *id = timer;
877     return 0;
878 }
879
880 void vlc_timer_destroy (vlc_timer_t timer)
881 {
882 #ifndef UNDER_CE
883     if (timer->handle != INVALID_HANDLE_VALUE)
884         DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
885 #else
886     if (timer->id)
887         timeKillEvent (timer->id);
888     /* FIXME: timers that have not yet completed will trigger use-after-free */
889 #endif
890     free (timer);
891 }
892
893 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
894                          mtime_t value, mtime_t interval)
895 {
896 #ifndef UNDER_CE
897     if (timer->handle != INVALID_HANDLE_VALUE)
898     {
899         DeleteTimerQueueTimer (NULL, timer->handle, NULL);
900         timer->handle = INVALID_HANDLE_VALUE;
901     }
902 #else
903     if (timer->id)
904     {
905         timeKillEvent (timer->id);
906         timer->id = 0;
907         timer->interval = 0;
908     }
909 #endif
910     if (value == 0)
911         return; /* Disarm */
912
913     if (absolute)
914         value -= mdate ();
915     value = (value + 999) / 1000;
916     interval = (interval + 999) / 1000;
917
918 #ifndef UNDER_CE
919     if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
920                                 value, interval, WT_EXECUTEDEFAULT))
921 #else
922     TIMECAPS caps;
923     timeGetDevCaps (&caps, sizeof(caps));
924
925     unsigned delay = value;
926     delay = __MAX(delay, caps.wPeriodMin);
927     delay = __MIN(delay, caps.wPeriodMax);
928
929     unsigned event = TIME_ONESHOT;
930
931     if (interval == delay)
932         event = TIME_PERIODIC;
933     else if (interval)
934         timer->interval = interval;
935
936     timer->id = timeSetEvent (delay, delay / 20, vlc_timer_do, (DWORD) timer,
937                               event);
938     if (!timer->id)
939 #endif
940         abort ();
941 }
942
943 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
944 {
945     (void)timer;
946     return 0;
947 }
948
949
950 /*** CPU ***/
951 unsigned vlc_GetCPUCount (void)
952 {
953 #ifndef UNDER_CE
954     DWORD process;
955     DWORD system;
956
957     if (GetProcessAffinityMask (GetCurrentProcess(), &process, &system))
958         return popcount (system);
959 #endif
960      return 1;
961 }