Qt4: playlist_item, forgotten dec_ref
[vlc.git] / 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 #include <limits.h>
38
39 static vlc_threadvar_t cancel_key;
40
41 /**
42  * Per-thread cancellation data
43  */
44 typedef struct vlc_cancel_t
45 {
46     vlc_cleanup_t *cleaners;
47 #ifdef UNDER_CE
48     HANDLE         cancel_event;
49 #endif
50     bool           killable;
51     bool           killed;
52 } vlc_cancel_t;
53
54 #ifndef UNDER_CE
55 # define VLC_CANCEL_INIT { NULL, true, false }
56 #else
57 # define VLC_CANCEL_INIT { NULL, NULL, true, false }
58 #endif
59
60 #ifdef UNDER_CE
61 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
62
63 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
64                                   DWORD delay)
65 {
66     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
67     if (nfo == NULL)
68     {
69         /* Main thread - cannot be cancelled anyway */
70         return WaitForMultipleObjects (count, handles, FALSE, delay);
71     }
72     HANDLE new_handles[count + 1];
73     memcpy(new_handles, handles, count * sizeof(HANDLE));
74     new_handles[count] = nfo->cancel_event;
75     DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
76                                            delay);
77     if (result == WAIT_OBJECT_0 + count)
78     {
79         vlc_cancel_self (NULL);
80         return WAIT_IO_COMPLETION;
81     }
82     else
83     {
84         return result;
85     }
86 }
87
88 DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
89 {
90     if (bAlertable)
91     {
92         DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
93         return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
94     }
95     else
96     {
97         Sleep(dwMilliseconds);
98         return 0;
99     }
100 }
101
102 DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
103                              BOOL bAlertable)
104 {
105     if (bAlertable)
106     {
107         /* The MSDN documentation specifies different return codes,
108          * but in practice they are the same. We just check that it
109          * remains so. */
110 #if WAIT_ABANDONED != WAIT_ABANDONED_0
111 # error Windows headers changed, code needs to be rewritten!
112 #endif
113         return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
114     }
115     else
116     {
117         return WaitForSingleObject (hHandle, dwMilliseconds);
118     }
119 }
120
121 DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
122                                 BOOL bWaitAll, DWORD dwMilliseconds,
123                                 BOOL bAlertable)
124 {
125     if (bAlertable)
126     {
127         /* We do not support the bWaitAll case */
128         assert (! bWaitAll);
129         return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
130     }
131     else
132     {
133         return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
134                                        dwMilliseconds);
135     }
136 }
137 #endif
138
139 static vlc_mutex_t super_mutex;
140
141 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
142 {
143     (void) hinstDll;
144     (void) lpvReserved;
145
146     switch (fdwReason)
147     {
148         case DLL_PROCESS_ATTACH:
149             vlc_mutex_init (&super_mutex);
150             vlc_threadvar_create (&cancel_key, free);
151             break;
152
153         case DLL_PROCESS_DETACH:
154             vlc_threadvar_delete( &cancel_key );
155             vlc_mutex_destroy (&super_mutex);
156             break;
157     }
158     return TRUE;
159 }
160
161 /*** Mutexes ***/
162 void vlc_mutex_init( vlc_mutex_t *p_mutex )
163 {
164     /* This creates a recursive mutex. This is OK as fast mutexes have
165      * no defined behavior in case of recursive locking. */
166     InitializeCriticalSection (&p_mutex->mutex);
167     p_mutex->initialized = 1;
168 }
169
170 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
171 {
172     InitializeCriticalSection( &p_mutex->mutex );
173     p_mutex->initialized = 1;
174 }
175
176
177 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
178 {
179     assert (InterlockedExchange (&p_mutex->initialized, -1) == 1);
180     DeleteCriticalSection (&p_mutex->mutex);
181 }
182
183 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
184 {
185     if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
186     { /* ^^ We could also lock super_mutex all the time... sluggish */
187         assert (p_mutex != &super_mutex); /* this one cannot be static */
188
189         vlc_mutex_lock (&super_mutex);
190         if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
191             vlc_mutex_init (p_mutex);
192         /* FIXME: destroy the mutex some time... */
193         vlc_mutex_unlock (&super_mutex);
194     }
195     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
196     EnterCriticalSection (&p_mutex->mutex);
197 }
198
199 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
200 {
201     if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
202     { /* ^^ We could also lock super_mutex all the time... sluggish */
203         assert (p_mutex != &super_mutex); /* this one cannot be static */
204
205         vlc_mutex_lock (&super_mutex);
206         if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
207             vlc_mutex_init (p_mutex);
208         /* FIXME: destroy the mutex some time... */
209         vlc_mutex_unlock (&super_mutex);
210     }
211     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
212     return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
213 }
214
215 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
216 {
217     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
218     LeaveCriticalSection (&p_mutex->mutex);
219 }
220
221 /*** Condition variables ***/
222 void vlc_cond_init( vlc_cond_t *p_condvar )
223 {
224     /* Create a manual-reset event (manual reset is needed for broadcast). */
225     *p_condvar = CreateEvent (NULL, TRUE, FALSE, NULL);
226     if (!*p_condvar)
227         abort();
228 }
229
230 void vlc_cond_destroy (vlc_cond_t *p_condvar)
231 {
232     CloseHandle (*p_condvar);
233 }
234
235 void vlc_cond_signal (vlc_cond_t *p_condvar)
236 {
237     /* NOTE: This will cause a broadcast, that is wrong.
238      * This will also wake up the next waiting thread if no threads are yet
239      * waiting, which is also wrong. However both of these issues are allowed
240      * by the provision for spurious wakeups. Better have too many wakeups
241      * than too few (= deadlocks). */
242     SetEvent (*p_condvar);
243 }
244
245 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
246 {
247     SetEvent (*p_condvar);
248 }
249
250 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
251 {
252     DWORD result;
253
254     do
255     {
256         vlc_testcancel ();
257         LeaveCriticalSection (&p_mutex->mutex);
258         result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE);
259         EnterCriticalSection (&p_mutex->mutex);
260     }
261     while (result == WAIT_IO_COMPLETION);
262
263     assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
264     assert (result != WAIT_FAILED);
265     ResetEvent (*p_condvar);
266 }
267
268 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
269                         mtime_t deadline)
270 {
271     DWORD result;
272
273     do
274     {
275         vlc_testcancel ();
276
277         mtime_t total = (deadline - mdate ())/1000;
278         if( total < 0 )
279             total = 0;
280
281         DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
282         LeaveCriticalSection (&p_mutex->mutex);
283         result = WaitForSingleObjectEx (*p_condvar, delay, TRUE);
284         EnterCriticalSection (&p_mutex->mutex);
285     }
286     while (result == WAIT_IO_COMPLETION);
287
288     assert (result != WAIT_ABANDONED);
289     assert (result != WAIT_FAILED);
290     ResetEvent (*p_condvar);
291
292     return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
293 }
294
295 /*** Read/write locks */
296 /* SRW (Slim Read Write) locks are available in Vista+ only */
297 void vlc_rwlock_init (vlc_rwlock_t *lock)
298 {
299     vlc_mutex_init (&lock->mutex);
300     vlc_cond_init (&lock->read_wait);
301     vlc_cond_init (&lock->write_wait);
302     lock->readers = 0; /* active readers */
303     lock->writers = 0; /* waiting or active writers */
304     lock->writer = 0; /* ID of active writer */
305 }
306
307 /**
308  * Destroys an initialized unused read/write lock.
309  */
310 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
311 {
312     vlc_cond_destroy (&lock->read_wait);
313     vlc_cond_destroy (&lock->write_wait);
314     vlc_mutex_destroy (&lock->mutex);
315 }
316
317 /**
318  * Acquires a read/write lock for reading. Recursion is allowed.
319  */
320 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
321 {
322     vlc_mutex_lock (&lock->mutex);
323     while (lock->writer != 0)
324         vlc_cond_wait (&lock->read_wait, &lock->mutex);
325     if (lock->readers == ULONG_MAX)
326         abort ();
327     lock->readers++;
328     vlc_mutex_unlock (&lock->mutex);
329 }
330
331 /**
332  * Acquires a read/write lock for writing. Recursion is not allowed.
333  */
334 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
335 {
336     vlc_mutex_lock (&lock->mutex);
337     if (lock->writers == ULONG_MAX)
338         abort ();
339     lock->writers++;
340     while ((lock->readers > 0) || (lock->writer != 0))
341         vlc_cond_wait (&lock->write_wait, &lock->mutex);
342     lock->writers--;
343     lock->writer = GetCurrentThreadId ();
344     vlc_mutex_unlock (&lock->mutex);
345 }
346
347 /**
348  * Releases a read/write lock.
349  */
350 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
351 {
352     vlc_mutex_lock (&lock->mutex);
353     if (lock->readers > 0)
354         lock->readers--; /* Read unlock */
355     else
356         lock->writer = 0; /* Write unlock */
357
358     if (lock->writers > 0)
359     {
360         if (lock->readers == 0)
361             vlc_cond_signal (&lock->write_wait);
362     }
363     else
364         vlc_cond_broadcast (&lock->read_wait);
365     vlc_mutex_unlock (&lock->mutex);
366 }
367
368 /*** Thread-specific variables (TLS) ***/
369 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
370 {
371 #warning FIXME: use destr() callback and stop leaking!
372     *p_tls = TlsAlloc();
373     return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
374 }
375
376 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
377 {
378     TlsFree (*p_tls);
379 }
380
381 /**
382  * Sets a thread-local variable.
383  * @param key thread-local variable key (created with vlc_threadvar_create())
384  * @param value new value for the variable for the calling thread
385  * @return 0 on success, a system error code otherwise.
386  */
387 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
388 {
389     return TlsSetValue (key, value) ? ENOMEM : 0;
390 }
391
392 /**
393  * Gets the value of a thread-local variable for the calling thread.
394  * This function cannot fail.
395  * @return the value associated with the given variable for the calling
396  * or NULL if there is no value.
397  */
398 void *vlc_threadvar_get (vlc_threadvar_t key)
399 {
400     return TlsGetValue (key);
401 }
402
403
404 /*** Threads ***/
405 void vlc_threads_setup (libvlc_int_t *p_libvlc)
406 {
407     (void) p_libvlc;
408 }
409
410 struct vlc_entry_data
411 {
412     void * (*func) (void *);
413     void *  data;
414 #ifdef UNDER_CE
415     HANDLE  cancel_event;
416 #endif
417 };
418
419 static unsigned __stdcall vlc_entry (void *p)
420 {
421     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
422     struct vlc_entry_data data;
423
424     memcpy (&data, p, sizeof (data));
425     free (p);
426
427 #ifdef UNDER_CE
428     cancel_data.cancel_event = data.cancel_event;
429 #endif
430
431     vlc_threadvar_set (cancel_key, &cancel_data);
432     data.func (data.data);
433     return 0;
434 }
435
436 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
437                int priority)
438 {
439     int err = ENOMEM;
440     HANDLE hThread;
441
442     struct vlc_entry_data *entry_data = malloc (sizeof (*entry_data));
443     if (entry_data == NULL)
444         return ENOMEM;
445     entry_data->func = entry;
446     entry_data->data = data;
447
448 #ifndef UNDER_CE
449     /* When using the MSVCRT C library you have to use the _beginthreadex
450      * function instead of CreateThread, otherwise you'll end up with
451      * memory leaks and the signal functions not working (see Microsoft
452      * Knowledge Base, article 104641) */
453     hThread = (HANDLE)(uintptr_t)
454         _beginthreadex (NULL, 0, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
455     if (! hThread)
456     {
457         err = errno;
458         goto error;
459     }
460
461     /* Thread closes the handle when exiting, duplicate it here
462      * to be on the safe side when joining. */
463     if (!DuplicateHandle (GetCurrentProcess (), hThread,
464                           GetCurrentProcess (), p_handle, 0, FALSE,
465                           DUPLICATE_SAME_ACCESS))
466     {
467         CloseHandle (hThread);
468         goto error;
469     }
470
471 #else
472     vlc_thread_t th = malloc (sizeof (*th));
473     if (th == NULL)
474         goto error;
475     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
476     if (th->cancel_event == NULL)
477     {
478         free (th);
479         goto error;
480     }
481     entry_data->cancel_event = th->cancel_event;
482
483     /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
484      * Thread handles act up, too. */
485     th->handle = CreateThread (NULL, 128*1024, vlc_entry, entry_data,
486                                CREATE_SUSPENDED, NULL);
487     if (th->handle == NULL)
488     {
489         CloseHandle (th->cancel_event);
490         free (th);
491         goto error;
492     }
493
494     *p_handle = th;
495     hThread = th->handle;
496
497 #endif
498
499     ResumeThread (hThread);
500     if (priority)
501         SetThreadPriority (hThread, priority);
502
503     return 0;
504
505 error:
506     free (entry_data);
507     return err;
508 }
509
510 void vlc_join (vlc_thread_t handle, void **result)
511 {
512 #ifdef UNDER_CE
513 # define handle handle->handle
514 #endif
515     do
516         vlc_testcancel ();
517     while (WaitForSingleObjectEx (handle, INFINITE, TRUE)
518                                                         == WAIT_IO_COMPLETION);
519
520     CloseHandle (handle);
521     assert (result == NULL); /* <- FIXME if ever needed */
522 #ifdef UNDER_CE
523 # undef handle
524     CloseHandle (handle->cancel_event);
525     free (handle);
526 #endif
527 }
528
529 void vlc_detach (vlc_thread_t handle)
530 {
531 #ifndef UNDER_CE
532     CloseHandle (handle);
533 #else
534     /* FIXME: handle->cancel_event leak */
535     CloseHandle (handle->handle);
536     free (handle);
537 #endif
538 }
539
540 /*** Thread cancellation ***/
541
542 /* APC procedure for thread cancellation */
543 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
544 {
545     (void)dummy;
546     vlc_control_cancel (VLC_DO_CANCEL);
547 }
548
549 void vlc_cancel (vlc_thread_t thread_id)
550 {
551 #ifndef UNDER_CE
552     QueueUserAPC (vlc_cancel_self, thread_id, 0);
553 #else
554     SetEvent (thread_id->cancel_event);
555 #endif
556 }
557
558 int vlc_savecancel (void)
559 {
560     int state;
561
562     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
563     if (nfo == NULL)
564         return false; /* Main thread - cannot be cancelled anyway */
565
566     state = nfo->killable;
567     nfo->killable = false;
568     return state;
569 }
570
571 void vlc_restorecancel (int state)
572 {
573     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
574     assert (state == false || state == true);
575
576     if (nfo == NULL)
577         return; /* Main thread - cannot be cancelled anyway */
578
579     assert (!nfo->killable);
580     nfo->killable = state != 0;
581 }
582
583 void vlc_testcancel (void)
584 {
585     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
586     if (nfo == NULL)
587         return; /* Main thread - cannot be cancelled anyway */
588
589     if (nfo->killable && nfo->killed)
590     {
591         for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
592              p->proc (p->data);
593 #ifndef UNDER_CE
594         _endthread ();
595 #else
596         ExitThread(0);
597 #endif
598     }
599 }
600
601 void vlc_control_cancel (int cmd, ...)
602 {
603     /* NOTE: This function only modifies thread-specific data, so there is no
604      * need to lock anything. */
605     va_list ap;
606
607     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
608     if (nfo == NULL)
609         return; /* Main thread - cannot be cancelled anyway */
610
611     va_start (ap, cmd);
612     switch (cmd)
613     {
614         case VLC_DO_CANCEL:
615             nfo->killed = true;
616             break;
617
618         case VLC_CLEANUP_PUSH:
619         {
620             /* cleaner is a pointer to the caller stack, no need to allocate
621              * and copy anything. As a nice side effect, this cannot fail. */
622             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
623             cleaner->next = nfo->cleaners;
624             nfo->cleaners = cleaner;
625             break;
626         }
627
628         case VLC_CLEANUP_POP:
629         {
630             nfo->cleaners = nfo->cleaners->next;
631             break;
632         }
633     }
634     va_end (ap);
635 }
636
637
638 /*** Timers ***/
639 struct vlc_timer
640 {
641     HANDLE handle;
642     void (*func) (void *);
643     void *data;
644 };
645
646 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
647 {
648     struct vlc_timer *timer = val;
649
650     assert (timeout);
651     timer->func (timer->data);
652 }
653
654 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
655 {
656     struct vlc_timer *timer = malloc (sizeof (*timer));
657
658     if (timer == NULL)
659         return ENOMEM;
660     timer->func = func;
661     timer->data = data;
662     timer->handle = INVALID_HANDLE_VALUE;
663     *id = timer;
664     return 0;
665 }
666
667 void vlc_timer_destroy (vlc_timer_t timer)
668 {
669     if (timer->handle != INVALID_HANDLE_VALUE)
670         DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
671     free (timer);
672 }
673
674 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
675                          mtime_t value, mtime_t interval)
676 {
677     if (timer->handle != INVALID_HANDLE_VALUE)
678     {
679         DeleteTimerQueueTimer (NULL, timer->handle, NULL);
680         timer->handle = INVALID_HANDLE_VALUE;
681     }
682     if (value == 0)
683         return; /* Disarm */
684
685     if (absolute)
686         value -= mdate ();
687     value = (value + 999) / 1000;
688     interval = (interval + 999) / 1000;
689     if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
690                                 value, interval, WT_EXECUTEDEFAULT))
691         abort ();
692 }
693
694 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
695 {
696     (void)timer;
697     return 0;
698 }