]> git.sesse.net Git - vlc/blob - src/misc/w32thread.c
a62e530b458d18ecb28c1a4cc869d225b44c9acb
[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 int 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 int 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 int 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     return *p_condvar ? 0 : ENOMEM;
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 /*** Thread-specific variables (TLS) ***/
296 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
297 {
298 #warning FIXME: use destr() callback and stop leaking!
299     *p_tls = TlsAlloc();
300     return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
301 }
302
303 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
304 {
305     TlsFree (*p_tls);
306 }
307
308 /**
309  * Sets a thread-local variable.
310  * @param key thread-local variable key (created with vlc_threadvar_create())
311  * @param value new value for the variable for the calling thread
312  * @return 0 on success, a system error code otherwise.
313  */
314 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
315 {
316     return TlsSetValue (key, value) ? ENOMEM : 0;
317 }
318
319 /**
320  * Gets the value of a thread-local variable for the calling thread.
321  * This function cannot fail.
322  * @return the value associated with the given variable for the calling
323  * or NULL if there is no value.
324  */
325 void *vlc_threadvar_get (vlc_threadvar_t key)
326 {
327     return TlsGetValue (key);
328 }
329
330
331 /*** Threads ***/
332 static unsigned __stdcall vlc_entry (void *data)
333 {
334     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
335     vlc_thread_t self = data;
336 #ifdef UNDER_CE
337     cancel_data.cancel_event = self->cancel_event;
338 #endif
339
340     vlc_threadvar_set (cancel_key, &cancel_data);
341     self->data = self->entry (self->data);
342     return 0;
343 }
344
345 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
346                int priority)
347 {
348     /* When using the MSVCRT C library you have to use the _beginthreadex
349      * function instead of CreateThread, otherwise you'll end up with
350      * memory leaks and the signal functions not working (see Microsoft
351      * Knowledge Base, article 104641) */
352     HANDLE hThread;
353     vlc_thread_t th = malloc (sizeof (*th));
354
355     if (th == NULL)
356         return ENOMEM;
357
358     th->data = data;
359     th->entry = entry;
360 #if defined( UNDER_CE )
361     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
362     if (th->cancel_event == NULL)
363     {
364         free(th);
365         return errno;
366     }
367     hThread = CreateThread (NULL, 128*1024, vlc_entry, th, CREATE_SUSPENDED, NULL);
368 #else
369     hThread = (HANDLE)(uintptr_t)
370         _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
371 #endif
372
373     if (hThread)
374     {
375 #ifndef UNDER_CE
376         /* Thread closes the handle when exiting, duplicate it here
377          * to be on the safe side when joining. */
378         if (!DuplicateHandle (GetCurrentProcess (), hThread,
379                               GetCurrentProcess (), &th->handle, 0, FALSE,
380                               DUPLICATE_SAME_ACCESS))
381         {
382             CloseHandle (hThread);
383             free (th);
384             return ENOMEM;
385         }
386 #else
387         th->handle = hThread;
388 #endif
389
390         ResumeThread (hThread);
391         if (priority)
392             SetThreadPriority (hThread, priority);
393
394         *p_handle = th;
395         return 0;
396     }
397     free (th);
398     return errno;
399 }
400
401 void vlc_join (vlc_thread_t handle, void **result)
402 {
403     do
404         vlc_testcancel ();
405     while (WaitForSingleObjectEx (handle->handle, INFINITE, TRUE)
406                                                         == WAIT_IO_COMPLETION);
407
408     CloseHandle (handle->handle);
409     if (result)
410         *result = handle->data;
411 #ifdef UNDER_CE
412     CloseHandle (handle->cancel_event);
413 #endif
414     free (handle);
415 }
416
417
418 /*** Thread cancellation ***/
419
420 /* APC procedure for thread cancellation */
421 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
422 {
423     (void)dummy;
424     vlc_control_cancel (VLC_DO_CANCEL);
425 }
426
427 void vlc_cancel (vlc_thread_t thread_id)
428 {
429 #ifndef UNDER_CE
430     QueueUserAPC (vlc_cancel_self, thread_id->handle, 0);
431 #else
432     SetEvent (thread_id->cancel_event);
433 #endif
434 }
435
436 int vlc_savecancel (void)
437 {
438     int state;
439
440     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
441     if (nfo == NULL)
442         return false; /* Main thread - cannot be cancelled anyway */
443
444     state = nfo->killable;
445     nfo->killable = false;
446     return state;
447 }
448
449 void vlc_restorecancel (int state)
450 {
451     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
452     assert (state == false || state == true);
453
454     if (nfo == NULL)
455         return; /* Main thread - cannot be cancelled anyway */
456
457     assert (!nfo->killable);
458     nfo->killable = state != 0;
459 }
460
461 void vlc_testcancel (void)
462 {
463     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
464     if (nfo == NULL)
465         return; /* Main thread - cannot be cancelled anyway */
466
467     if (nfo->killable && nfo->killed)
468     {
469         for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
470              p->proc (p->data);
471 #ifndef UNDER_CE
472         _endthread ();
473 #else
474         ExitThread(0);
475 #endif
476     }
477 }
478
479 void vlc_control_cancel (int cmd, ...)
480 {
481     /* NOTE: This function only modifies thread-specific data, so there is no
482      * need to lock anything. */
483     va_list ap;
484
485     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
486     if (nfo == NULL)
487         return; /* Main thread - cannot be cancelled anyway */
488
489     va_start (ap, cmd);
490     switch (cmd)
491     {
492         case VLC_DO_CANCEL:
493             nfo->killed = true;
494             break;
495
496         case VLC_CLEANUP_PUSH:
497         {
498             /* cleaner is a pointer to the caller stack, no need to allocate
499              * and copy anything. As a nice side effect, this cannot fail. */
500             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
501             cleaner->next = nfo->cleaners;
502             nfo->cleaners = cleaner;
503             break;
504         }
505
506         case VLC_CLEANUP_POP:
507         {
508             nfo->cleaners = nfo->cleaners->next;
509             break;
510         }
511     }
512     va_end (ap);
513 }