]> git.sesse.net Git - vlc/blob - src/misc/w32thread.c
Implement thread semaphores
[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  *          Pierre Ynard
13  *
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.
18  *
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.
23  *
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  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34
35 #include "libvlc.h"
36 #include <stdarg.h>
37 #include <assert.h>
38 #include <limits.h>
39 #ifdef UNDER_CE
40 # include <mmsystem.h>
41 #endif
42
43 static vlc_threadvar_t cancel_key;
44
45 /**
46  * Per-thread cancellation data
47  */
48 typedef struct vlc_cancel_t
49 {
50     vlc_cleanup_t *cleaners;
51 #ifdef UNDER_CE
52     HANDLE         cancel_event;
53 #endif
54     bool           killable;
55     bool           killed;
56 } vlc_cancel_t;
57
58 #ifndef UNDER_CE
59 # define VLC_CANCEL_INIT { NULL, true, false }
60 #else
61 # define VLC_CANCEL_INIT { NULL, NULL, true, false }
62 #endif
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     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
71     if (nfo == 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] = nfo->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 (NULL);
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 static vlc_mutex_t super_mutex;
144
145 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
146 {
147     (void) hinstDll;
148     (void) lpvReserved;
149
150     switch (fdwReason)
151     {
152         case DLL_PROCESS_ATTACH:
153             vlc_mutex_init (&super_mutex);
154             vlc_threadvar_create (&cancel_key, free);
155             break;
156
157         case DLL_PROCESS_DETACH:
158             vlc_threadvar_delete( &cancel_key );
159             vlc_mutex_destroy (&super_mutex);
160             break;
161     }
162     return TRUE;
163 }
164
165 /*** Mutexes ***/
166 void vlc_mutex_init( vlc_mutex_t *p_mutex )
167 {
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;
172 }
173
174 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
175 {
176     InitializeCriticalSection( &p_mutex->mutex );
177     p_mutex->initialized = 1;
178 }
179
180
181 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
182 {
183     assert (InterlockedExchange (&p_mutex->initialized, -1) == 1);
184     DeleteCriticalSection (&p_mutex->mutex);
185 }
186
187 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
188 {
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 */
192
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);
198     }
199     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
200     EnterCriticalSection (&p_mutex->mutex);
201 }
202
203 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
204 {
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 */
208
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);
214     }
215     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
216     return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
217 }
218
219 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
220 {
221     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
222     LeaveCriticalSection (&p_mutex->mutex);
223 }
224
225 /*** Condition variables ***/
226 void vlc_cond_init( vlc_cond_t *p_condvar )
227 {
228     /* Create a manual-reset event (manual reset is needed for broadcast). */
229     *p_condvar = CreateEvent (NULL, TRUE, FALSE, NULL);
230     if (!*p_condvar)
231         abort();
232 }
233
234 void vlc_cond_destroy (vlc_cond_t *p_condvar)
235 {
236     CloseHandle (*p_condvar);
237 }
238
239 void vlc_cond_signal (vlc_cond_t *p_condvar)
240 {
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);
247 }
248
249 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
250 {
251     SetEvent (*p_condvar);
252 }
253
254 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
255 {
256     DWORD result;
257
258     do
259     {
260         vlc_testcancel ();
261         LeaveCriticalSection (&p_mutex->mutex);
262         result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE);
263         EnterCriticalSection (&p_mutex->mutex);
264     }
265     while (result == WAIT_IO_COMPLETION);
266
267     assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
268     assert (result != WAIT_FAILED);
269     ResetEvent (*p_condvar);
270 }
271
272 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
273                         mtime_t deadline)
274 {
275     DWORD result;
276
277     do
278     {
279         vlc_testcancel ();
280
281         mtime_t total = (deadline - mdate ())/1000;
282         if( total < 0 )
283             total = 0;
284
285         DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
286         LeaveCriticalSection (&p_mutex->mutex);
287         result = WaitForSingleObjectEx (*p_condvar, delay, TRUE);
288         EnterCriticalSection (&p_mutex->mutex);
289     }
290     while (result == WAIT_IO_COMPLETION);
291
292     assert (result != WAIT_ABANDONED);
293     assert (result != WAIT_FAILED);
294     ResetEvent (*p_condvar);
295
296     return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
297 }
298
299 /*** Semaphore ***/
300 void vlc_sem_init (vlc_sem_t *sem, unsigned value)
301 {
302     *sem = CreateSemaphore (NULL, value, 0x7fffffff, NULL);
303     if (*sem == NULL)
304         abort ();
305 }
306
307 void vlc_sem_destroy (vlc_sem_t *sem)
308 {
309     CloseHandle (*sem);
310 }
311
312 int vlc_sem_post (vlc_sem_t *sem)
313 {
314     ReleaseSemaphore (*sem, 1, NULL);
315     return 0; /* FIXME */
316 }
317
318 void vlc_sem_wait (vlc_sem_t *sem)
319 {
320     DWORD result;
321
322     do
323     {
324         vlc_testcancel ();
325         result = WaitForSingleObjectEx (*sem, INFINITE, TRUE);
326     }
327     while (result == WAIT_IO_COMPLETION);
328 }
329
330 /*** Read/write locks */
331 /* SRW (Slim Read Write) locks are available in Vista+ only */
332 void vlc_rwlock_init (vlc_rwlock_t *lock)
333 {
334     vlc_mutex_init (&lock->mutex);
335     vlc_cond_init (&lock->read_wait);
336     vlc_cond_init (&lock->write_wait);
337     lock->readers = 0; /* active readers */
338     lock->writers = 0; /* waiting or active writers */
339     lock->writer = 0; /* ID of active writer */
340 }
341
342 /**
343  * Destroys an initialized unused read/write lock.
344  */
345 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
346 {
347     vlc_cond_destroy (&lock->read_wait);
348     vlc_cond_destroy (&lock->write_wait);
349     vlc_mutex_destroy (&lock->mutex);
350 }
351
352 /**
353  * Acquires a read/write lock for reading. Recursion is allowed.
354  */
355 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
356 {
357     vlc_mutex_lock (&lock->mutex);
358     while (lock->writer != 0)
359         vlc_cond_wait (&lock->read_wait, &lock->mutex);
360     if (lock->readers == ULONG_MAX)
361         abort ();
362     lock->readers++;
363     vlc_mutex_unlock (&lock->mutex);
364 }
365
366 /**
367  * Acquires a read/write lock for writing. Recursion is not allowed.
368  */
369 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
370 {
371     vlc_mutex_lock (&lock->mutex);
372     if (lock->writers == ULONG_MAX)
373         abort ();
374     lock->writers++;
375     while ((lock->readers > 0) || (lock->writer != 0))
376         vlc_cond_wait (&lock->write_wait, &lock->mutex);
377     lock->writers--;
378     lock->writer = GetCurrentThreadId ();
379     vlc_mutex_unlock (&lock->mutex);
380 }
381
382 /**
383  * Releases a read/write lock.
384  */
385 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
386 {
387     vlc_mutex_lock (&lock->mutex);
388     if (lock->readers > 0)
389         lock->readers--; /* Read unlock */
390     else
391         lock->writer = 0; /* Write unlock */
392
393     if (lock->writers > 0)
394     {
395         if (lock->readers == 0)
396             vlc_cond_signal (&lock->write_wait);
397     }
398     else
399         vlc_cond_broadcast (&lock->read_wait);
400     vlc_mutex_unlock (&lock->mutex);
401 }
402
403 /*** Thread-specific variables (TLS) ***/
404 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
405 {
406 #warning FIXME: use destr() callback and stop leaking!
407     VLC_UNUSED( destr );
408
409     *p_tls = TlsAlloc();
410     return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
411 }
412
413 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
414 {
415     TlsFree (*p_tls);
416 }
417
418 /**
419  * Sets a thread-local variable.
420  * @param key thread-local variable key (created with vlc_threadvar_create())
421  * @param value new value for the variable for the calling thread
422  * @return 0 on success, a system error code otherwise.
423  */
424 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
425 {
426     return TlsSetValue (key, value) ? ENOMEM : 0;
427 }
428
429 /**
430  * Gets the value of a thread-local variable for the calling thread.
431  * This function cannot fail.
432  * @return the value associated with the given variable for the calling
433  * or NULL if there is no value.
434  */
435 void *vlc_threadvar_get (vlc_threadvar_t key)
436 {
437     return TlsGetValue (key);
438 }
439
440
441 /*** Threads ***/
442 void vlc_threads_setup (libvlc_int_t *p_libvlc)
443 {
444     (void) p_libvlc;
445 }
446
447 struct vlc_entry_data
448 {
449     void * (*func) (void *);
450     void *  data;
451 #ifdef UNDER_CE
452     HANDLE  cancel_event;
453 #endif
454 };
455
456 static unsigned __stdcall vlc_entry (void *p)
457 {
458     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
459     struct vlc_entry_data data;
460
461     memcpy (&data, p, sizeof (data));
462     free (p);
463
464 #ifdef UNDER_CE
465     cancel_data.cancel_event = data.cancel_event;
466 #endif
467
468     vlc_threadvar_set (cancel_key, &cancel_data);
469     data.func (data.data);
470     return 0;
471 }
472
473 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
474                int priority)
475 {
476     int err = ENOMEM;
477     HANDLE hThread;
478
479     struct vlc_entry_data *entry_data = malloc (sizeof (*entry_data));
480     if (entry_data == NULL)
481         return ENOMEM;
482     entry_data->func = entry;
483     entry_data->data = data;
484
485 #ifndef UNDER_CE
486     /* When using the MSVCRT C library you have to use the _beginthreadex
487      * function instead of CreateThread, otherwise you'll end up with
488      * memory leaks and the signal functions not working (see Microsoft
489      * Knowledge Base, article 104641) */
490     hThread = (HANDLE)(uintptr_t)
491         _beginthreadex (NULL, 0, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
492     if (! hThread)
493     {
494         err = errno;
495         goto error;
496     }
497
498     /* Thread closes the handle when exiting, duplicate it here
499      * to be on the safe side when joining. */
500     if (!DuplicateHandle (GetCurrentProcess (), hThread,
501                           GetCurrentProcess (), p_handle, 0, FALSE,
502                           DUPLICATE_SAME_ACCESS))
503     {
504         CloseHandle (hThread);
505         goto error;
506     }
507
508 #else
509     vlc_thread_t th = malloc (sizeof (*th));
510     if (th == NULL)
511         goto error;
512     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
513     if (th->cancel_event == NULL)
514     {
515         free (th);
516         goto error;
517     }
518     entry_data->cancel_event = th->cancel_event;
519
520     /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
521      * Thread handles act up, too. */
522     th->handle = CreateThread (NULL, 128*1024, vlc_entry, entry_data,
523                                CREATE_SUSPENDED, NULL);
524     if (th->handle == NULL)
525     {
526         CloseHandle (th->cancel_event);
527         free (th);
528         goto error;
529     }
530
531     *p_handle = th;
532     hThread = th->handle;
533
534 #endif
535
536     ResumeThread (hThread);
537     if (priority)
538         SetThreadPriority (hThread, priority);
539
540     return 0;
541
542 error:
543     free (entry_data);
544     return err;
545 }
546
547 void vlc_join (vlc_thread_t handle, void **result)
548 {
549 #ifdef UNDER_CE
550 # define handle handle->handle
551 #endif
552     do
553         vlc_testcancel ();
554     while (WaitForSingleObjectEx (handle, INFINITE, TRUE)
555                                                         == WAIT_IO_COMPLETION);
556
557     CloseHandle (handle);
558     assert (result == NULL); /* <- FIXME if ever needed */
559 #ifdef UNDER_CE
560 # undef handle
561     CloseHandle (handle->cancel_event);
562     free (handle);
563 #endif
564 }
565
566 void vlc_detach (vlc_thread_t handle)
567 {
568 #ifndef UNDER_CE
569     CloseHandle (handle);
570 #else
571     /* FIXME: handle->cancel_event leak */
572     CloseHandle (handle->handle);
573     free (handle);
574 #endif
575 }
576
577 /*** Thread cancellation ***/
578
579 /* APC procedure for thread cancellation */
580 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
581 {
582     (void)dummy;
583     vlc_control_cancel (VLC_DO_CANCEL);
584 }
585
586 void vlc_cancel (vlc_thread_t thread_id)
587 {
588 #ifndef UNDER_CE
589     QueueUserAPC (vlc_cancel_self, thread_id, 0);
590 #else
591     SetEvent (thread_id->cancel_event);
592 #endif
593 }
594
595 int vlc_savecancel (void)
596 {
597     int state;
598
599     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
600     if (nfo == NULL)
601         return false; /* Main thread - cannot be cancelled anyway */
602
603     state = nfo->killable;
604     nfo->killable = false;
605     return state;
606 }
607
608 void vlc_restorecancel (int state)
609 {
610     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
611     assert (state == false || state == true);
612
613     if (nfo == NULL)
614         return; /* Main thread - cannot be cancelled anyway */
615
616     assert (!nfo->killable);
617     nfo->killable = state != 0;
618 }
619
620 void vlc_testcancel (void)
621 {
622     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
623     if (nfo == NULL)
624         return; /* Main thread - cannot be cancelled anyway */
625
626     if (nfo->killable && nfo->killed)
627     {
628         for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
629              p->proc (p->data);
630 #ifndef UNDER_CE
631         _endthread ();
632 #else
633         ExitThread(0);
634 #endif
635     }
636 }
637
638 void vlc_control_cancel (int cmd, ...)
639 {
640     /* NOTE: This function only modifies thread-specific data, so there is no
641      * need to lock anything. */
642     va_list ap;
643
644     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
645     if (nfo == NULL)
646         return; /* Main thread - cannot be cancelled anyway */
647
648     va_start (ap, cmd);
649     switch (cmd)
650     {
651         case VLC_DO_CANCEL:
652             nfo->killed = true;
653             break;
654
655         case VLC_CLEANUP_PUSH:
656         {
657             /* cleaner is a pointer to the caller stack, no need to allocate
658              * and copy anything. As a nice side effect, this cannot fail. */
659             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
660             cleaner->next = nfo->cleaners;
661             nfo->cleaners = cleaner;
662             break;
663         }
664
665         case VLC_CLEANUP_POP:
666         {
667             nfo->cleaners = nfo->cleaners->next;
668             break;
669         }
670     }
671     va_end (ap);
672 }
673
674
675 /*** Timers ***/
676 struct vlc_timer
677 {
678 #ifndef UNDER_CE
679     HANDLE handle;
680 #else
681     unsigned id;
682     unsigned interval;
683 #endif
684     void (*func) (void *);
685     void *data;
686 };
687
688 #ifndef UNDER_CE
689 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
690 {
691     struct vlc_timer *timer = val;
692
693     assert (timeout);
694     timer->func (timer->data);
695 }
696 #else
697 static void CALLBACK vlc_timer_do (unsigned timer_id, unsigned msg,
698                                    DWORD_PTR user, DWORD_PTR unused1,
699                                    DWORD_PTR unused2)
700 {
701     struct vlc_timer *timer = (struct vlc_timer *) user;
702     assert (timer_id == timer->id);
703     (void) msg;
704     (void) unused1;
705     (void) unused2;
706
707     timer->func (timer->data);
708
709     if (timer->interval)
710     {
711         mtime_t interval = timer->interval * 1000;
712         vlc_timer_schedule (timer, false, interval, interval);
713     }
714 }
715 #endif
716
717 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
718 {
719     struct vlc_timer *timer = malloc (sizeof (*timer));
720
721     if (timer == NULL)
722         return ENOMEM;
723     timer->func = func;
724     timer->data = data;
725 #ifndef UNDER_CE
726     timer->handle = INVALID_HANDLE_VALUE;
727 #else
728     timer->id = 0;
729     timer->interval = 0;
730 #endif
731     *id = timer;
732     return 0;
733 }
734
735 void vlc_timer_destroy (vlc_timer_t timer)
736 {
737 #ifndef UNDER_CE
738     if (timer->handle != INVALID_HANDLE_VALUE)
739         DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
740 #else
741     if (timer->id)
742         timeKillEvent (timer->id);
743     /* FIXME: timers that have not yet completed will trigger use-after-free */
744 #endif
745     free (timer);
746 }
747
748 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
749                          mtime_t value, mtime_t interval)
750 {
751 #ifndef UNDER_CE
752     if (timer->handle != INVALID_HANDLE_VALUE)
753     {
754         DeleteTimerQueueTimer (NULL, timer->handle, NULL);
755         timer->handle = INVALID_HANDLE_VALUE;
756     }
757 #else
758     if (timer->id)
759     {
760         timeKillEvent (timer->id);
761         timer->id = 0;
762         timer->interval = 0;
763     }
764 #endif
765     if (value == 0)
766         return; /* Disarm */
767
768     if (absolute)
769         value -= mdate ();
770     value = (value + 999) / 1000;
771     interval = (interval + 999) / 1000;
772
773 #ifndef UNDER_CE
774     if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
775                                 value, interval, WT_EXECUTEDEFAULT))
776 #else
777     TIMECAPS caps;
778     timeGetDevCaps (&caps, sizeof(caps));
779
780     unsigned delay = value;
781     delay = __MAX(delay, caps.wPeriodMin);
782     delay = __MIN(delay, caps.wPeriodMax);
783
784     unsigned event = TIME_ONESHOT;
785
786     if (interval == delay)
787         event = TIME_PERIODIC;
788     else if (interval)
789         timer->interval = interval;
790
791     timer->id = timeSetEvent (delay, delay / 20, vlc_timer_do, (DWORD) timer,
792                               event);
793     if (!timer->id)
794 #endif
795         abort ();
796 }
797
798 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
799 {
800     (void)timer;
801     return 0;
802 }