7ed00af1352deaa0d32598c62bb64a00a2bea27f
[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
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     return 0;
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     return 0;
175 }
176
177
178 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
179 {
180     assert (InterlockedExchange (&p_mutex->initialized, -1) == 1);
181     DeleteCriticalSection (&p_mutex->mutex);
182 }
183
184 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
185 {
186     if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
187     { /* ^^ We could also lock super_mutex all the time... sluggish */
188         assert (p_mutex != &super_mutex); /* this one cannot be static */
189
190         vlc_mutex_lock (&super_mutex);
191         if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
192             vlc_mutex_init (p_mutex);
193         /* FIXME: destroy the mutex some time... */
194         vlc_mutex_unlock (&super_mutex);
195     }
196     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
197     EnterCriticalSection (&p_mutex->mutex);
198 }
199
200 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
201 {
202     if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
203     { /* ^^ We could also lock super_mutex all the time... sluggish */
204         assert (p_mutex != &super_mutex); /* this one cannot be static */
205
206         vlc_mutex_lock (&super_mutex);
207         if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
208             vlc_mutex_init (p_mutex);
209         /* FIXME: destroy the mutex some time... */
210         vlc_mutex_unlock (&super_mutex);
211     }
212     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
213     return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
214 }
215
216 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
217 {
218     assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
219     LeaveCriticalSection (&p_mutex->mutex);
220 }
221
222 /*** Condition variables ***/
223 void vlc_cond_init( vlc_cond_t *p_condvar )
224 {
225     /* Create a manual-reset event (manual reset is needed for broadcast). */
226     *p_condvar = CreateEvent (NULL, TRUE, FALSE, NULL);
227     if (!*p_condvar)
228         abort();
229 }
230
231 void vlc_cond_destroy (vlc_cond_t *p_condvar)
232 {
233     CloseHandle (*p_condvar);
234 }
235
236 void vlc_cond_signal (vlc_cond_t *p_condvar)
237 {
238     /* NOTE: This will cause a broadcast, that is wrong.
239      * This will also wake up the next waiting thread if no threads are yet
240      * waiting, which is also wrong. However both of these issues are allowed
241      * by the provision for spurious wakeups. Better have too many wakeups
242      * than too few (= deadlocks). */
243     SetEvent (*p_condvar);
244 }
245
246 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
247 {
248     SetEvent (*p_condvar);
249 }
250
251 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
252 {
253     DWORD result;
254
255     do
256     {
257         vlc_testcancel ();
258         LeaveCriticalSection (&p_mutex->mutex);
259         result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE);
260         EnterCriticalSection (&p_mutex->mutex);
261     }
262     while (result == WAIT_IO_COMPLETION);
263
264     assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
265     assert (result != WAIT_FAILED);
266     ResetEvent (*p_condvar);
267 }
268
269 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
270                         mtime_t deadline)
271 {
272     DWORD result;
273
274     do
275     {
276         vlc_testcancel ();
277
278         mtime_t total = (deadline - mdate ())/1000;
279         if( total < 0 )
280             total = 0;
281
282         DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
283         LeaveCriticalSection (&p_mutex->mutex);
284         result = WaitForSingleObjectEx (*p_condvar, delay, TRUE);
285         EnterCriticalSection (&p_mutex->mutex);
286     }
287     while (result == WAIT_IO_COMPLETION);
288
289     assert (result != WAIT_ABANDONED);
290     assert (result != WAIT_FAILED);
291     ResetEvent (*p_condvar);
292
293     return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
294 }
295
296 /*** Thread-specific variables (TLS) ***/
297 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
298 {
299 #warning FIXME: use destr() callback and stop leaking!
300     *p_tls = TlsAlloc();
301     return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
302 }
303
304 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
305 {
306     TlsFree (*p_tls);
307 }
308
309 /**
310  * Sets a thread-local variable.
311  * @param key thread-local variable key (created with vlc_threadvar_create())
312  * @param value new value for the variable for the calling thread
313  * @return 0 on success, a system error code otherwise.
314  */
315 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
316 {
317     return TlsSetValue (key, value) ? ENOMEM : 0;
318 }
319
320 /**
321  * Gets the value of a thread-local variable for the calling thread.
322  * This function cannot fail.
323  * @return the value associated with the given variable for the calling
324  * or NULL if there is no value.
325  */
326 void *vlc_threadvar_get (vlc_threadvar_t key)
327 {
328     return TlsGetValue (key);
329 }
330
331
332 /*** Threads ***/
333 static unsigned __stdcall vlc_entry (void *data)
334 {
335     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
336     vlc_thread_t self = data;
337 #ifdef UNDER_CE
338     cancel_data.cancel_event = self->cancel_event;
339 #endif
340
341     vlc_threadvar_set (cancel_key, &cancel_data);
342     self->data = self->entry (self->data);
343     return 0;
344 }
345
346 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
347                int priority)
348 {
349     /* When using the MSVCRT C library you have to use the _beginthreadex
350      * function instead of CreateThread, otherwise you'll end up with
351      * memory leaks and the signal functions not working (see Microsoft
352      * Knowledge Base, article 104641) */
353     HANDLE hThread;
354     vlc_thread_t th = malloc (sizeof (*th));
355
356     if (th == NULL)
357         return ENOMEM;
358
359     th->data = data;
360     th->entry = entry;
361 #if defined( UNDER_CE )
362     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
363     if (th->cancel_event == NULL)
364     {
365         free(th);
366         return errno;
367     }
368     hThread = CreateThread (NULL, 128*1024, vlc_entry, th, CREATE_SUSPENDED, NULL);
369 #else
370     hThread = (HANDLE)(uintptr_t)
371         _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
372 #endif
373
374     if (hThread)
375     {
376 #ifndef UNDER_CE
377         /* Thread closes the handle when exiting, duplicate it here
378          * to be on the safe side when joining. */
379         if (!DuplicateHandle (GetCurrentProcess (), hThread,
380                               GetCurrentProcess (), &th->handle, 0, FALSE,
381                               DUPLICATE_SAME_ACCESS))
382         {
383             CloseHandle (hThread);
384             free (th);
385             return ENOMEM;
386         }
387 #else
388         th->handle = hThread;
389 #endif
390
391         ResumeThread (hThread);
392         if (priority)
393             SetThreadPriority (hThread, priority);
394
395         *p_handle = th;
396         return 0;
397     }
398     free (th);
399     return errno;
400 }
401
402 void vlc_join (vlc_thread_t handle, void **result)
403 {
404     do
405         vlc_testcancel ();
406     while (WaitForSingleObjectEx (handle->handle, INFINITE, TRUE)
407                                                         == WAIT_IO_COMPLETION);
408
409     CloseHandle (handle->handle);
410     if (result)
411         *result = handle->data;
412 #ifdef UNDER_CE
413     CloseHandle (handle->cancel_event);
414 #endif
415     free (handle);
416 }
417
418
419 /*** Thread cancellation ***/
420
421 /* APC procedure for thread cancellation */
422 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
423 {
424     (void)dummy;
425     vlc_control_cancel (VLC_DO_CANCEL);
426 }
427
428 void vlc_cancel (vlc_thread_t thread_id)
429 {
430 #ifndef UNDER_CE
431     QueueUserAPC (vlc_cancel_self, thread_id->handle, 0);
432 #else
433     SetEvent (thread_id->cancel_event);
434 #endif
435 }
436
437 int vlc_savecancel (void)
438 {
439     int state;
440
441     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
442     if (nfo == NULL)
443         return false; /* Main thread - cannot be cancelled anyway */
444
445     state = nfo->killable;
446     nfo->killable = false;
447     return state;
448 }
449
450 void vlc_restorecancel (int state)
451 {
452     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
453     assert (state == false || state == true);
454
455     if (nfo == NULL)
456         return; /* Main thread - cannot be cancelled anyway */
457
458     assert (!nfo->killable);
459     nfo->killable = state != 0;
460 }
461
462 void vlc_testcancel (void)
463 {
464     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
465     if (nfo == NULL)
466         return; /* Main thread - cannot be cancelled anyway */
467
468     if (nfo->killable && nfo->killed)
469     {
470         for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
471              p->proc (p->data);
472 #ifndef UNDER_CE
473         _endthread ();
474 #else
475         ExitThread(0);
476 #endif
477     }
478 }
479
480 void vlc_control_cancel (int cmd, ...)
481 {
482     /* NOTE: This function only modifies thread-specific data, so there is no
483      * need to lock anything. */
484     va_list ap;
485
486     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
487     if (nfo == NULL)
488         return; /* Main thread - cannot be cancelled anyway */
489
490     va_start (ap, cmd);
491     switch (cmd)
492     {
493         case VLC_DO_CANCEL:
494             nfo->killed = true;
495             break;
496
497         case VLC_CLEANUP_PUSH:
498         {
499             /* cleaner is a pointer to the caller stack, no need to allocate
500              * and copy anything. As a nice side effect, this cannot fail. */
501             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
502             cleaner->next = nfo->cleaners;
503             nfo->cleaners = cleaner;
504             break;
505         }
506
507         case VLC_CLEANUP_POP:
508         {
509             nfo->cleaners = nfo->cleaners->next;
510             break;
511         }
512     }
513     va_end (ap);
514 }