]> git.sesse.net Git - vlc/blob - src/misc/threads.c
* ./src/misc/variables.c: fixed a deadlock in command variables handling;
[vlc] / src / misc / threads.c
1 /*****************************************************************************
2  * threads.c : threads implementation for the VideoLAN client
3  *****************************************************************************
4  * Copyright (C) 1999, 2000, 2001, 2002 VideoLAN
5  * $Id: threads.c,v 1.23 2002/10/16 10:31:58 sam Exp $
6  *
7  * Authors: Jean-Marc Dressler <polux@via.ecp.fr>
8  *          Samuel Hocevar <sam@zoy.org>
9  *          Gildas Bazin <gbazin@netcourrier.com>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  * 
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
24  *****************************************************************************/
25
26 #include <vlc/vlc.h>
27
28 #include <stdlib.h>
29
30 #define VLC_THREADS_UNINITIALIZED  0
31 #define VLC_THREADS_PENDING        1
32 #define VLC_THREADS_ERROR          2
33 #define VLC_THREADS_READY          3
34
35 /*****************************************************************************
36  * Global mutex for lazy initialization of the threads system
37  *****************************************************************************/
38 static volatile int i_initializations = 0;
39
40 #if defined( PTH_INIT_IN_PTH_H )
41 #elif defined( ST_INIT_IN_ST_H )
42 #elif defined( WIN32 )
43 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )
44     static pthread_mutex_t once_mutex = PTHREAD_MUTEX_INITIALIZER;
45 #elif defined( HAVE_CTHREADS_H )
46 #elif defined( HAVE_KERNEL_SCHEDULER_H )
47 #endif
48
49 /*****************************************************************************
50  * Global variable for named mutexes
51  *****************************************************************************/
52 typedef struct vlc_namedmutex_t vlc_namedmutex_t;
53 struct vlc_namedmutex_t
54 {
55     vlc_mutex_t lock;
56
57     char *psz_name;
58     int i_usage;
59     vlc_namedmutex_t *p_next;
60 };
61
62 /*****************************************************************************
63  * vlc_threads_init: initialize threads system
64  *****************************************************************************
65  * This function requires lazy initialization of a global lock in order to
66  * keep the library really thread-safe. Some architectures don't support this
67  * and thus do not guarantee the complete reentrancy.
68  *****************************************************************************/
69 int __vlc_threads_init( vlc_object_t *p_this )
70 {
71     static volatile int i_status = VLC_THREADS_UNINITIALIZED;
72
73     libvlc_t *p_libvlc = (libvlc_t *)p_this;
74     int i_ret = VLC_SUCCESS;
75
76     /* If we have lazy mutex initialization, use it. Otherwise, we just
77      * hope nothing wrong happens. */
78 #if defined( PTH_INIT_IN_PTH_H )
79 #elif defined( ST_INIT_IN_ST_H )
80 #elif defined( WIN32 )
81     HINSTANCE hInstLib;
82 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )
83     pthread_mutex_lock( &once_mutex );
84 #elif defined( HAVE_CTHREADS_H )
85 #elif defined( HAVE_KERNEL_SCHEDULER_H )
86 #endif
87
88     if( i_status == VLC_THREADS_UNINITIALIZED )
89     {
90         i_status = VLC_THREADS_PENDING;
91
92         /* We should be safe now. Do all the initialization stuff we want. */
93         vlc_object_create( p_libvlc, VLC_OBJECT_ROOT );
94         p_libvlc->b_ready = VLC_FALSE;
95
96 #if defined( PTH_INIT_IN_PTH_H )
97         i_ret = pth_init();
98
99 #elif defined( ST_INIT_IN_ST_H )
100         i_ret = st_init();
101
102 #elif defined( WIN32 )
103         /* Dynamically get the address of SignalObjectAndWait */
104         if( GetVersion() < 0x80000000 )
105         {
106             /* We are running on NT/2K/XP, we can use SignalObjectAndWait */
107             hInstLib = LoadLibrary( "kernel32" );
108             if( hInstLib )
109             {
110                 p_libvlc->SignalObjectAndWait =
111                     (SIGNALOBJECTANDWAIT)GetProcAddress( hInstLib,
112                                                      "SignalObjectAndWait" );
113             }
114         }
115         else
116         {
117             p_libvlc->SignalObjectAndWait = NULL;
118         }
119
120         p_libvlc->b_fast_mutex = 0;
121         p_libvlc->i_win9x_cv = 0;
122
123 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )
124 #elif defined( HAVE_CTHREADS_H )
125 #elif defined( HAVE_KERNEL_SCHEDULER_H )
126 #endif
127
128         if( i_ret )
129         {
130             i_status = VLC_THREADS_ERROR;
131         }
132         else
133         {
134             i_initializations++;
135             i_status = VLC_THREADS_READY;
136         }
137     }
138     else
139     {
140         /* Just increment the initialization count */
141         i_initializations++;
142     }
143
144     /* If we have lazy mutex initialization support, unlock the mutex;
145      * otherwize, do a naive wait loop. */
146 #if defined( PTH_INIT_IN_PTH_H )
147     while( i_status == VLC_THREADS_PENDING ) msleep( THREAD_SLEEP );
148 #elif defined( ST_INIT_IN_ST_H )
149     while( i_status == VLC_THREADS_PENDING ) msleep( THREAD_SLEEP );
150 #elif defined( WIN32 )
151     while( i_status == VLC_THREADS_PENDING ) msleep( THREAD_SLEEP );
152 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )
153     pthread_mutex_unlock( &once_mutex );
154 #elif defined( HAVE_CTHREADS_H )
155     while( i_status == VLC_THREADS_PENDING ) msleep( THREAD_SLEEP );
156 #elif defined( HAVE_KERNEL_SCHEDULER_H )
157     while( i_status == VLC_THREADS_PENDING ) msleep( THREAD_SLEEP );
158 #endif
159
160     if( i_status != VLC_THREADS_READY )
161     {
162         return VLC_ETHREAD;
163     }
164
165     return i_ret;
166 }
167
168 /*****************************************************************************
169  * vlc_threads_end: stop threads system
170  *****************************************************************************
171  * FIXME: This function is far from being threadsafe. We should undo exactly
172  * what we did above in vlc_threads_init.
173  *****************************************************************************/
174 int __vlc_threads_end( vlc_object_t *p_this )
175 {
176 #if defined( PTH_INIT_IN_PTH_H )
177     i_initializations--;
178     if( i_initializations == 0 )
179     {
180         return pth_kill();
181     }
182
183 #elif defined( ST_INIT_IN_ST_H )
184     i_initializations--;
185
186 #elif defined( WIN32 )
187     i_initializations--;
188
189 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )
190     pthread_mutex_lock( &once_mutex );
191     i_initializations--;
192     pthread_mutex_unlock( &once_mutex );
193
194 #elif defined( HAVE_CTHREADS_H )
195     i_initializations--;
196
197 #elif defined( HAVE_KERNEL_SCHEDULER_H )
198     i_initializations--;
199
200 #endif
201     return VLC_SUCCESS;
202 }
203
204 /*****************************************************************************
205  * Prototype for GPROF wrapper
206  *****************************************************************************/
207 #ifdef GPROF
208 /* Wrapper function for profiling */
209 static void *      vlc_thread_wrapper ( void *p_wrapper );
210
211 #   ifdef WIN32
212
213 #       define ITIMER_REAL 1
214 #       define ITIMER_PROF 2
215
216 struct itimerval
217 {
218     struct timeval it_value;
219     struct timeval it_interval;
220 };  
221
222 int setitimer(int kind, const struct itimerval* itnew, struct itimerval* itold);
223 #   endif /* WIN32 */
224
225 typedef struct wrapper_t
226 {
227     /* Data lock access */
228     vlc_mutex_t lock;
229     vlc_cond_t  wait;
230     
231     /* Data used to spawn the real thread */
232     vlc_thread_func_t func;
233     void *p_data;
234     
235     /* Profiling timer passed to the thread */
236     struct itimerval itimer;
237     
238 } wrapper_t;
239
240 #endif /* GPROF */
241
242 /*****************************************************************************
243  * vlc_mutex_init: initialize a mutex
244  *****************************************************************************/
245 int __vlc_mutex_init( vlc_object_t *p_this, vlc_mutex_t *p_mutex )
246 {
247     p_mutex->p_this = p_this;
248
249 #if defined( PTH_INIT_IN_PTH_H )
250     return pth_mutex_init( &p_mutex->mutex );
251
252 #elif defined( ST_INIT_IN_ST_H )
253     p_mutex->mutex = st_mutex_new();
254     return ( p_mutex->mutex == NULL ) ? errno : 0;
255
256 #elif defined( WIN32 )
257     /* We use mutexes on WinNT/2K/XP because we can use the SignalObjectAndWait
258      * function and have a 100% correct vlc_cond_wait() implementation.
259      * As this function is not available on Win9x, we can use the faster
260      * CriticalSections */
261     if( p_this->p_libvlc->SignalObjectAndWait &&
262         !p_this->p_libvlc->b_fast_mutex )
263     {
264         /* We are running on NT/2K/XP, we can use SignalObjectAndWait */
265         p_mutex->mutex = CreateMutex( 0, FALSE, 0 );
266         return ( p_mutex->mutex != NULL ? 0 : 1 );
267     }
268     else
269     {
270         p_mutex->mutex = NULL;
271         InitializeCriticalSection( &p_mutex->csection );
272         return 0;
273     }
274
275 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )
276 #   if defined(DEBUG) && defined(SYS_LINUX)
277     {
278         /* Create error-checking mutex to detect problems more easily. */
279         pthread_mutexattr_t attr;
280         int                 i_result;
281
282         pthread_mutexattr_init( &attr );
283         pthread_mutexattr_setkind_np( &attr, PTHREAD_MUTEX_ERRORCHECK_NP );
284         i_result = pthread_mutex_init( &p_mutex->mutex, &attr );
285         pthread_mutexattr_destroy( &attr );
286         return( i_result );
287     }
288 #   endif
289     return pthread_mutex_init( &p_mutex->mutex, NULL );
290
291 #elif defined( HAVE_CTHREADS_H )
292     mutex_init( p_mutex );
293     return 0;
294
295 #elif defined( HAVE_KERNEL_SCHEDULER_H )
296     /* check the arguments and whether it's already been initialized */
297     if( p_mutex == NULL )
298     {
299         return B_BAD_VALUE;
300     }
301
302     if( p_mutex->init == 9999 )
303     {
304         return EALREADY;
305     }
306
307     p_mutex->lock = create_sem( 1, "BeMutex" );
308     if( p_mutex->lock < B_NO_ERROR )
309     {
310         return( -1 );
311     }
312
313     p_mutex->init = 9999;
314     return B_OK;
315
316 #endif
317 }
318
319 /*****************************************************************************
320  * vlc_mutex_destroy: destroy a mutex, inner version
321  *****************************************************************************/
322 int __vlc_mutex_destroy( char * psz_file, int i_line, vlc_mutex_t *p_mutex )
323 {
324     int i_result;
325     /* In case of error : */
326     int i_thread = -1;
327     const char * psz_error = "";
328
329 #if defined( PTH_INIT_IN_PTH_H )
330     return 0;
331
332 #elif defined( ST_INIT_IN_ST_H )
333     i_result = st_mutex_destroy( p_mutex->mutex );
334
335 #elif defined( WIN32 )
336     if( p_mutex->mutex )
337     {
338         CloseHandle( p_mutex->mutex );
339     }
340     else
341     {
342         DeleteCriticalSection( &p_mutex->csection );
343     }
344     return 0;
345
346 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )    
347     i_result = pthread_mutex_destroy( &p_mutex->mutex );
348     if ( i_result )
349     {
350         i_thread = (int)pthread_self();
351         psz_error = strerror(i_result);
352     }
353
354 #elif defined( HAVE_CTHREADS_H )
355     return 0;
356
357 #elif defined( HAVE_KERNEL_SCHEDULER_H )
358     if( p_mutex->init == 9999 )
359     {
360         delete_sem( p_mutex->lock );
361     }
362
363     p_mutex->init = 0;
364     return B_OK;
365 #endif    
366
367     if( i_result )
368     {
369         msg_Err( p_mutex->p_this,
370                  "thread %d: mutex_destroy failed at %s:%d (%d:%s)",
371                  i_thread, psz_file, i_line, i_result, psz_error );
372     }
373     return i_result;
374 }
375
376 /*****************************************************************************
377  * vlc_cond_init: initialize a condition
378  *****************************************************************************/
379 int __vlc_cond_init( vlc_object_t *p_this, vlc_cond_t *p_condvar )
380 {
381     p_condvar->p_this = p_this;
382
383 #if defined( PTH_INIT_IN_PTH_H )
384     return pth_cond_init( &p_condvar->cond );
385
386 #elif defined( ST_INIT_IN_ST_H )
387     p_condvar->cond = st_cond_new();
388     return ( p_condvar->cond == NULL ) ? errno : 0;
389
390 #elif defined( WIN32 )
391     /* Initialize counter */
392     p_condvar->i_waiting_threads = 0;
393
394     /* Misc init */
395     p_condvar->i_win9x_cv = p_this->p_libvlc->i_win9x_cv;
396     p_condvar->SignalObjectAndWait = p_this->p_libvlc->SignalObjectAndWait;
397
398     if( (p_condvar->SignalObjectAndWait && !p_this->p_libvlc->b_fast_mutex)
399         || p_condvar->i_win9x_cv == 0 )
400     {
401         /* Create an auto-reset event. */
402         p_condvar->event = CreateEvent( NULL,   /* no security */
403                                         FALSE,  /* auto-reset event */
404                                         FALSE,  /* start non-signaled */
405                                         NULL ); /* unnamed */
406
407         p_condvar->semaphore = NULL;
408         return !p_condvar->event;
409     }
410     else
411     {
412         p_condvar->semaphore = CreateSemaphore( NULL,       /* no security */
413                                                 0,          /* initial count */
414                                                 0x7fffffff, /* max count */
415                                                 NULL );     /* unnamed */
416
417         if( p_condvar->i_win9x_cv == 1 )
418             /* Create a manual-reset event initially signaled. */
419             p_condvar->event = CreateEvent( NULL, TRUE, TRUE, NULL );
420         else
421             /* Create a auto-reset event. */
422             p_condvar->event = CreateEvent( NULL, FALSE, FALSE, NULL );
423
424         InitializeCriticalSection( &p_condvar->csection );
425
426         return !p_condvar->semaphore || !p_condvar->event;
427     }
428
429 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )
430     return pthread_cond_init( &p_condvar->cond, NULL );
431
432 #elif defined( HAVE_CTHREADS_H )
433     /* condition_init() */
434     spin_lock_init( &p_condvar->lock );
435     cthread_queue_init( &p_condvar->queue );
436     p_condvar->name = 0;
437     p_condvar->implications = 0;
438
439     return 0;
440
441 #elif defined( HAVE_KERNEL_SCHEDULER_H )
442     if( !p_condvar )
443     {
444         return B_BAD_VALUE;
445     }
446
447     if( p_condvar->init == 9999 )
448     {
449         return EALREADY;
450     }
451
452     p_condvar->thread = -1;
453     p_condvar->init = 9999;
454     return 0;
455 #endif
456 }
457
458 /*****************************************************************************
459  * vlc_cond_destroy: destroy a condition, inner version
460  *****************************************************************************/
461 int __vlc_cond_destroy( char * psz_file, int i_line, vlc_cond_t *p_condvar )
462 {
463     int i_result;
464     /* In case of error : */
465     int i_thread = -1;
466     const char * psz_error = "";
467
468 #if defined( PTH_INIT_IN_PTH_H )
469     return 0;
470
471 #elif defined( ST_INIT_IN_ST_H )
472     i_result = st_cond_destroy( p_condvar->cond );
473
474 #elif defined( WIN32 )
475     if( !p_condvar->semaphore )
476         i_result = !CloseHandle( p_condvar->event );
477     else
478         i_result = !CloseHandle( p_condvar->event )
479           || !CloseHandle( p_condvar->semaphore );
480
481 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )
482     i_result = pthread_cond_destroy( &p_condvar->cond );
483     if ( i_result )
484     {
485         i_thread = (int)pthread_self();
486         psz_error = strerror(i_result);
487     }
488
489 #elif defined( HAVE_CTHREADS_H )
490     return 0;
491
492 #elif defined( HAVE_KERNEL_SCHEDULER_H )
493     p_condvar->init = 0;
494     return 0;
495 #endif
496
497     if( i_result )
498     {
499         msg_Err( p_condvar->p_this,
500                  "thread %d: cond_destroy failed at %s:%d (%d:%s)",
501                  i_thread, psz_file, i_line, i_result, psz_error );
502     }
503     return i_result;
504 }
505
506 /*****************************************************************************
507  * vlc_thread_create: create a thread, inner version
508  *****************************************************************************
509  * Note that i_priority is only taken into account on platforms supporting
510  * userland real-time priority threads.
511  *****************************************************************************/
512 int __vlc_thread_create( vlc_object_t *p_this, char * psz_file, int i_line,
513                          char *psz_name, void * ( *func ) ( void * ),
514                          int i_priority, vlc_bool_t b_wait )
515 {
516     int i_ret;
517
518     vlc_mutex_lock( &p_this->object_lock );
519
520 #ifdef GPROF
521     wrapper_t wrapper;
522
523     /* Initialize the wrapper structure */
524     wrapper.func = func;
525     wrapper.p_data = (void *)p_this;
526     getitimer( ITIMER_PROF, &wrapper.itimer );
527     vlc_mutex_init( p_this, &wrapper.lock );
528     vlc_cond_init( p_this, &wrapper.wait );
529     vlc_mutex_lock( &wrapper.lock );
530
531     /* Alter user-passed data so that we call the wrapper instead
532      * of the real function */
533     p_data = &wrapper;
534     func = vlc_thread_wrapper;
535 #endif
536
537 #if defined( PTH_INIT_IN_PTH_H )
538     p_this->thread_id = pth_spawn( PTH_ATTR_DEFAULT, func, (void *)p_this );
539     i_ret = 0;
540
541 #elif defined( ST_INIT_IN_ST_H )
542     p_this->thread_id = st_thread_create( func, (void *)p_this, 1, 0 );
543     i_ret = 0;
544     
545 #elif defined( WIN32 )
546     {
547         unsigned threadID;
548         /* When using the MSVCRT C library you have to use the _beginthreadex
549          * function instead of CreateThread, otherwise you'll end up with
550          * memory leaks and the signal functions not working */
551         p_this->thread_id =
552                 (HANDLE)_beginthreadex( NULL, 0, (PTHREAD_START) func, 
553                                         (void *)p_this, 0, &threadID );
554     }
555
556     if ( p_this->thread_id && i_priority )
557     {
558         if ( !SetThreadPriority(p_this->thread_id, i_priority) )
559         {
560             msg_Warn( p_this, "couldn't set a faster priority" );
561             i_priority = 0;
562         }
563     }
564
565     i_ret = ( p_this->thread_id ? 0 : 1 );
566
567 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )
568     i_ret = pthread_create( &p_this->thread_id, NULL, func, (void *)p_this );
569
570     if ( i_priority )
571     {
572         struct sched_param param;
573         memset( &param, 0, sizeof(struct sched_param) );
574         param.sched_priority = i_priority;
575         if ( pthread_setschedparam( p_this->thread_id, SCHED_RR, &param ) )
576         {
577             msg_Warn( p_this, "couldn't go to real-time priority" );
578             i_priority = 0;
579         }
580     }
581
582 #elif defined( HAVE_CTHREADS_H )
583     p_this->thread_id = cthread_fork( (cthread_fn_t)func, (any_t)p_this );
584     i_ret = 0;
585
586 #elif defined( HAVE_KERNEL_SCHEDULER_H )
587     p_this->thread_id = spawn_thread( (thread_func)func, psz_name,
588                                       B_NORMAL_PRIORITY, (void *)p_this );
589     i_ret = resume_thread( p_this->thread_id );
590
591 #endif
592
593 #ifdef GPROF
594     if( i_ret == 0 )
595     {
596         vlc_cond_wait( &wrapper.wait, &wrapper.lock );
597     }
598
599     vlc_mutex_unlock( &wrapper.lock );
600     vlc_mutex_destroy( &wrapper.lock );
601     vlc_cond_destroy( &wrapper.wait );
602 #endif
603
604     if( i_ret == 0 )
605     {
606         if( b_wait )
607         {
608             msg_Dbg( p_this, "waiting for thread completion" );
609             vlc_cond_wait( &p_this->object_wait, &p_this->object_lock );
610         }
611
612         p_this->b_thread = 1;
613
614         msg_Dbg( p_this, "thread %d (%s) created at priority %d (%s:%d)",
615                  p_this->thread_id, psz_name, i_priority,
616                  psz_file, i_line );
617
618         vlc_mutex_unlock( &p_this->object_lock );
619     }
620     else
621     {
622         msg_Err( p_this, "%s thread could not be created at %s:%d (%s)",
623                          psz_name, psz_file, i_line, strerror(i_ret) );
624         vlc_mutex_unlock( &p_this->object_lock );
625     }
626
627     return i_ret;
628 }
629
630 /*****************************************************************************
631  * vlc_thread_ready: tell the parent thread we were successfully spawned
632  *****************************************************************************/
633 void __vlc_thread_ready( vlc_object_t *p_this )
634 {
635     vlc_mutex_lock( &p_this->object_lock );
636     vlc_cond_signal( &p_this->object_wait );
637     vlc_mutex_unlock( &p_this->object_lock );
638 }
639
640 /*****************************************************************************
641  * vlc_thread_join: wait until a thread exits, inner version
642  *****************************************************************************/
643 void __vlc_thread_join( vlc_object_t *p_this, char * psz_file, int i_line )
644 {
645     int i_ret = 0;
646
647 #if defined( PTH_INIT_IN_PTH_H )
648     i_ret = pth_join( p_this->thread_id, NULL );
649
650 #elif defined( ST_INIT_IN_ST_H )
651     i_ret = st_thread_join( p_this->thread_id, NULL );
652     
653 #elif defined( WIN32 )
654     WaitForSingleObject( p_this->thread_id, INFINITE );
655
656 #elif defined( PTHREAD_COND_T_IN_PTHREAD_H )
657     i_ret = pthread_join( p_this->thread_id, NULL );
658
659 #elif defined( HAVE_CTHREADS_H )
660     cthread_join( p_this->thread_id );
661     i_ret = 1;
662
663 #elif defined( HAVE_KERNEL_SCHEDULER_H )
664     int32 exit_value;
665     wait_for_thread( p_this->thread_id, &exit_value );
666
667 #endif
668
669     if( i_ret )
670     {
671         msg_Err( p_this, "thread_join(%d) failed at %s:%d (%s)",
672                          p_this->thread_id, psz_file, i_line, strerror(i_ret) );
673     }
674     else
675     {
676         msg_Dbg( p_this, "thread %d joined (%s:%d)",
677                          p_this->thread_id, psz_file, i_line );
678     }
679
680     p_this->b_thread = 0;
681 }
682
683 /*****************************************************************************
684  * vlc_thread_wrapper: wrapper around thread functions used when profiling.
685  *****************************************************************************/
686 #ifdef GPROF
687 static void *vlc_thread_wrapper( void *p_wrapper )
688 {
689     /* Put user data in thread-local variables */
690     void *            p_data = ((wrapper_t*)p_wrapper)->p_data;
691     vlc_thread_func_t func   = ((wrapper_t*)p_wrapper)->func;
692
693     /* Set the profile timer value */
694     setitimer( ITIMER_PROF, &((wrapper_t*)p_wrapper)->itimer, NULL );
695
696     /* Tell the calling thread that we don't need its data anymore */
697     vlc_mutex_lock( &((wrapper_t*)p_wrapper)->lock );
698     vlc_cond_signal( &((wrapper_t*)p_wrapper)->wait );
699     vlc_mutex_unlock( &((wrapper_t*)p_wrapper)->lock );
700
701     /* Call the real function */
702     return func( p_data );
703 }
704 #endif