X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fmisc%2Fthreads.c;h=4c597082da521e775f711923ee10abf4d80a1cf5;hb=aa6b5c552d671fc1fce4ca38e5aff41ae259c28e;hp=f5773c366c5b56cba338af512e750925077aaba8;hpb=f213a1d69ab8208359c69284c6dd82d426511656;p=vlc diff --git a/src/misc/threads.c b/src/misc/threads.c index f5773c366c..33a76e4759 100644 --- a/src/misc/threads.c +++ b/src/misc/threads.c @@ -1,18 +1,19 @@ /***************************************************************************** * threads.c : threads implementation for the VideoLAN client ***************************************************************************** - * Copyright (C) 1999, 2000, 2001, 2002 VideoLAN - * $Id: threads.c,v 1.16 2002/09/17 14:56:13 sam Exp $ + * Copyright (C) 1999-2008 the VideoLAN team + * $Id$ * * Authors: Jean-Marc Dressler * Samuel Hocevar * Gildas Bazin + * Clément Sténac * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -20,10 +21,22 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ -#include +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "libvlc.h" +#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif +#include #define VLC_THREADS_UNINITIALIZED 0 #define VLC_THREADS_PENDING 1 @@ -31,60 +44,108 @@ #define VLC_THREADS_READY 3 /***************************************************************************** - * Prototype for GPROF wrapper + * Global mutex for lazy initialization of the threads system *****************************************************************************/ -#ifdef GPROF -/* Wrapper function for profiling */ -static void * vlc_thread_wrapper ( void *p_wrapper ); +static volatile unsigned i_initializations = 0; -# ifdef WIN32 +#if defined( LIBVLC_USE_PTHREAD ) +# include + +static pthread_mutex_t once_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +/** + * Global process-wide VLC object. + * Contains inter-instance data, such as the module cache and global mutexes. + */ +static libvlc_global_data_t *p_root; + +libvlc_global_data_t *vlc_global( void ) +{ + assert( i_initializations > 0 ); + return p_root; +} -# define ITIMER_REAL 1 -# define ITIMER_PROF 2 +#ifndef NDEBUG +/** + * Object running the current thread + */ +static vlc_threadvar_t thread_object_key; -struct itimerval +vlc_object_t *vlc_threadobj (void) { - struct timeval it_value; - struct timeval it_interval; -}; + return vlc_threadvar_get (&thread_object_key); +} +#endif -int setitimer(int kind, const struct itimerval* itnew, struct itimerval* itold); -# endif /* WIN32 */ +vlc_threadvar_t msg_context_global_key; -typedef struct wrapper_t +#if defined(LIBVLC_USE_PTHREAD) +static inline unsigned long vlc_threadid (void) { - /* Data lock access */ - vlc_mutex_t lock; - vlc_cond_t wait; - - /* Data used to spawn the real thread */ - vlc_thread_func_t func; - void *p_data; - - /* Profiling timer passed to the thread */ - struct itimerval itimer; - -} wrapper_t; - -#endif /* GPROF */ + union { pthread_t th; unsigned long int i; } v = { }; + v.th = pthread_self (); + return v.i; +} + +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) +# include +#endif /***************************************************************************** - * Global mutexes for lazy initialization of the threads system + * vlc_thread_fatal: Report an error from the threading layer + ***************************************************************************** + * This is mostly meant for debugging. *****************************************************************************/ -static volatile int i_initializations = 0; +void vlc_pthread_fatal (const char *action, int error, + const char *file, unsigned line) +{ + fprintf (stderr, "LibVLC fatal error %s in thread %lu at %s:%u: %d\n", + action, vlc_threadid (), file, line, error); + + /* Sometimes strerror_r() crashes too, so make sure we print an error + * message before we invoke it */ +#ifdef __GLIBC__ + /* Avoid the strerror_r() prototype brain damage in glibc */ + errno = error; + fprintf (stderr, " Error message: %m at:\n"); +#else + char buf[1000]; + const char *msg; + + switch (strerror_r (error, buf, sizeof (buf))) + { + case 0: + msg = buf; + break; + case ERANGE: /* should never happen */ + msg = "unknwon (too big to display)"; + break; + default: + msg = "unknown (invalid error number)"; + break; + } + fprintf (stderr, " Error message: %s\n", msg); +#endif + fflush (stderr); -#if defined( PTH_INIT_IN_PTH_H ) - /* Unimplemented */ -#elif defined( ST_INIT_IN_ST_H ) - /* Unimplemented */ -#elif defined( WIN32 ) - /* Unimplemented */ -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) - static pthread_mutex_t once_mutex = PTHREAD_MUTEX_INITIALIZER; -#elif defined( HAVE_CTHREADS_H ) - /* Unimplemented */ -#elif defined( HAVE_KERNEL_SCHEDULER_H ) - /* Unimplemented */ +#ifdef HAVE_BACKTRACE + void *stack[20]; + int len = backtrace (stack, sizeof (stack) / sizeof (stack[0])); + backtrace_symbols_fd (stack, len, 2); +#endif + + abort (); +} +#else +void vlc_pthread_fatal (const char *action, int error, + const char *file, unsigned line) +{ + (void)action; (void)error; (void)file; (void)line; + abort(); +} + +static vlc_threadvar_t cancel_key; #endif /***************************************************************************** @@ -94,195 +155,112 @@ static volatile int i_initializations = 0; * keep the library really thread-safe. Some architectures don't support this * and thus do not guarantee the complete reentrancy. *****************************************************************************/ -int __vlc_threads_init( vlc_object_t *p_this ) +int vlc_threads_init( void ) { - static volatile int i_status = VLC_THREADS_UNINITIALIZED; - int i_ret = 0; + int i_ret = VLC_SUCCESS; -#if defined( PTH_INIT_IN_PTH_H ) - /* Unimplemented */ -#elif defined( ST_INIT_IN_ST_H ) - /* Unimplemented */ -#elif defined( WIN32 ) - HINSTANCE hInstLib; -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) + /* If we have lazy mutex initialization, use it. Otherwise, we just + * hope nothing wrong happens. */ +#if defined( LIBVLC_USE_PTHREAD ) pthread_mutex_lock( &once_mutex ); -#elif defined( HAVE_CTHREADS_H ) - /* Unimplemented */ -#elif defined( HAVE_KERNEL_SCHEDULER_H ) - /* Unimplemented */ #endif - if( i_status == VLC_THREADS_UNINITIALIZED ) + if( i_initializations == 0 ) { - i_status = VLC_THREADS_PENDING; - -#if defined( PTH_INIT_IN_PTH_H ) - i_ret = pth_init(); -#elif defined( ST_INIT_IN_ST_H ) - i_ret = st_init(); -#elif defined( WIN32 ) - /* dynamically get the address of SignalObjectAndWait */ - if( GetVersion() < 0x80000000 ) + p_root = vlc_custom_create( (vlc_object_t *)NULL, sizeof( *p_root ), + VLC_OBJECT_GENERIC, "root" ); + if( p_root == NULL ) { - /* We are running on NT/2K/XP, we can use SignalObjectAndWait */ - hInstLib = LoadLibrary( "kernel32" ); - if( hInstLib ) - { - p_this->p_vlc->SignalObjectAndWait = - (SIGNALOBJECTANDWAIT)GetProcAddress( hInstLib, - "SignalObjectAndWait" ); - } + i_ret = VLC_ENOMEM; + goto out; } - else - { - p_this->p_vlc->SignalObjectAndWait = NULL; - } - - p_this->p_vlc->b_fast_mutex = 0; - p_this->p_vlc->i_win9x_cv = 0; -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) - /* Unimplemented */ -#elif defined( HAVE_CTHREADS_H ) - /* Unimplemented */ -#elif defined( HAVE_KERNEL_SCHEDULER_H ) - /* Unimplemented */ + /* We should be safe now. Do all the initialization stuff we want. */ +#ifndef NDEBUG + vlc_threadvar_create( &thread_object_key, NULL ); +#endif + vlc_threadvar_create( &msg_context_global_key, msg_StackDestroy ); +#ifndef LIBVLC_USE_PTHREAD + vlc_threadvar_create( &cancel_key, free ); #endif - - if( i_ret ) - { - i_status = VLC_THREADS_ERROR; - } - else - { - vlc_mutex_init( p_this, p_this->p_vlc->p_global_lock ); - i_status = VLC_THREADS_READY; - } - } - else - { - i_ret = ( i_status == VLC_THREADS_READY ); } - i_initializations++; -#if defined( PTH_INIT_IN_PTH_H ) - /* Unimplemented */ -#elif defined( ST_INIT_IN_ST_H ) - /* Unimplemented */ -#elif defined( WIN32 ) - /* Unimplemented */ -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) +out: + /* If we have lazy mutex initialization support, unlock the mutex. + * Otherwize, we are screwed. */ +#if defined( LIBVLC_USE_PTHREAD ) pthread_mutex_unlock( &once_mutex ); - return i_ret; -#elif defined( HAVE_CTHREADS_H ) - /* Unimplemented */ -#elif defined( HAVE_KERNEL_SCHEDULER_H ) - /* Unimplemented */ #endif - /* Wait until the other thread has initialized the thread library */ - while( i_status == VLC_THREADS_PENDING ) - { - msleep( THREAD_SLEEP ); - } - - return i_status == VLC_THREADS_READY; + return i_ret; } /***************************************************************************** * vlc_threads_end: stop threads system ***************************************************************************** - * FIXME: This function is far from being threadsafe. We should undo exactly - * what we did above in vlc_threads_init. + * FIXME: This function is far from being threadsafe. *****************************************************************************/ -int __vlc_threads_end( vlc_object_t *p_this ) +void vlc_threads_end( void ) { -#if defined( PTH_INIT_IN_PTH_H ) - i_initializations--; - if( i_initializations == 0 ) - { - return pth_kill(); - } - return 0; - -#elif defined( ST_INIT_IN_ST_H ) - i_initializations--; - return 0; - -#elif defined( WIN32 ) - i_initializations--; - return 0; - -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) +#if defined( LIBVLC_USE_PTHREAD ) pthread_mutex_lock( &once_mutex ); - i_initializations--; - pthread_mutex_unlock( &once_mutex ); - return 0; +#endif -#elif defined( HAVE_CTHREADS_H ) - i_initializations--; - return 0; + assert( i_initializations > 0 ); -#elif defined( HAVE_KERNEL_SCHEDULER_H ) + if( i_initializations == 1 ) + { + vlc_object_release( p_root ); +#ifndef LIBVLC_USE_PTHREAD + vlc_threadvar_delete( &cancel_key ); +#endif + vlc_threadvar_delete( &msg_context_global_key ); +#ifndef NDEBUG + vlc_threadvar_delete( &thread_object_key ); +#endif + } i_initializations--; - return 0; +#if defined( LIBVLC_USE_PTHREAD ) + pthread_mutex_unlock( &once_mutex ); #endif } +#if defined (__GLIBC__) && (__GLIBC_MINOR__ < 6) +/* This is not prototyped under glibc, though it exists. */ +int pthread_mutexattr_setkind_np( pthread_mutexattr_t *attr, int kind ); +#endif + /***************************************************************************** * vlc_mutex_init: initialize a mutex *****************************************************************************/ -int __vlc_mutex_init( vlc_object_t *p_this, vlc_mutex_t *p_mutex ) +int vlc_mutex_init( vlc_mutex_t *p_mutex ) { - p_mutex->p_this = p_this; - -#if defined( PTH_INIT_IN_PTH_H ) - return pth_mutex_init( &p_mutex->mutex ); - -#elif defined( ST_INIT_IN_ST_H ) - p_mutex->mutex = st_mutex_new(); - return ( p_mutex == NULL ) ? errno : 0; +#if defined( LIBVLC_USE_PTHREAD ) + pthread_mutexattr_t attr; + int i_result; + + pthread_mutexattr_init( &attr ); + +# ifndef NDEBUG + /* Create error-checking mutex to detect problems more easily. */ +# if defined (__GLIBC__) && (__GLIBC_MINOR__ < 6) + pthread_mutexattr_setkind_np( &attr, PTHREAD_MUTEX_ERRORCHECK_NP ); +# else + pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_ERRORCHECK ); +# endif +# endif + i_result = pthread_mutex_init( p_mutex, &attr ); + pthread_mutexattr_destroy( &attr ); + return i_result; +#elif defined( UNDER_CE ) + InitializeCriticalSection( &p_mutex->csection ); + return 0; #elif defined( WIN32 ) - /* We use mutexes on WinNT/2K/XP because we can use the SignalObjectAndWait - * function and have a 100% correct vlc_cond_wait() implementation. - * As this function is not available on Win9x, we can use the faster - * CriticalSections */ - if( p_this->p_vlc->SignalObjectAndWait && !p_this->p_vlc->b_fast_mutex ) - { - /* We are running on NT/2K/XP, we can use SignalObjectAndWait */ - p_mutex->mutex = CreateMutex( 0, FALSE, 0 ); - return ( p_mutex->mutex != NULL ? 0 : 1 ); - } - else - { - p_mutex->mutex = NULL; - InitializeCriticalSection( &p_mutex->csection ); - return 0; - } - -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) -# if defined(DEBUG) && defined(SYS_LINUX) - { - /* Create error-checking mutex to detect problems more easily. */ - pthread_mutexattr_t attr; - int i_result; - - pthread_mutexattr_init( &attr ); - pthread_mutexattr_setkind_np( &attr, PTHREAD_MUTEX_ERRORCHECK_NP ); - i_result = pthread_mutex_init( &p_mutex->mutex, &attr ); - pthread_mutexattr_destroy( &attr ); - return( i_result ); - } -# endif - return pthread_mutex_init( &p_mutex->mutex, NULL ); - -#elif defined( HAVE_CTHREADS_H ) - mutex_init( p_mutex ); - return 0; + *p_mutex = CreateMutex( 0, FALSE, 0 ); + return (*p_mutex != NULL) ? 0 : ENOMEM; #elif defined( HAVE_KERNEL_SCHEDULER_H ) /* check the arguments and whether it's already been initialized */ @@ -308,126 +286,95 @@ int __vlc_mutex_init( vlc_object_t *p_this, vlc_mutex_t *p_mutex ) #endif } +/***************************************************************************** + * vlc_mutex_init: initialize a recursive mutex (Do not use) + *****************************************************************************/ +int vlc_mutex_init_recursive( vlc_mutex_t *p_mutex ) +{ +#if defined( LIBVLC_USE_PTHREAD ) + pthread_mutexattr_t attr; + int i_result; + + pthread_mutexattr_init( &attr ); +# if defined (__GLIBC__) && (__GLIBC_MINOR__ < 6) + pthread_mutexattr_setkind_np( &attr, PTHREAD_MUTEX_RECURSIVE_NP ); +# else + pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); +# endif + i_result = pthread_mutex_init( p_mutex, &attr ); + pthread_mutexattr_destroy( &attr ); + return( i_result ); +#elif defined( WIN32 ) + /* Create mutex returns a recursive mutex */ + *p_mutex = CreateMutex( 0, FALSE, 0 ); + return (*p_mutex != NULL) ? 0 : ENOMEM; +#else +# error Unimplemented! +#endif +} + + /***************************************************************************** * vlc_mutex_destroy: destroy a mutex, inner version *****************************************************************************/ -int __vlc_mutex_destroy( char * psz_file, int i_line, vlc_mutex_t *p_mutex ) +void __vlc_mutex_destroy( const char * psz_file, int i_line, vlc_mutex_t *p_mutex ) { - int i_result; - /* In case of error : */ - int i_thread = -1; - const char * psz_error = ""; +#if defined( LIBVLC_USE_PTHREAD ) + int val = pthread_mutex_destroy( p_mutex ); + VLC_THREAD_ASSERT ("destroying mutex"); -#if defined( PTH_INIT_IN_PTH_H ) - return 0; +#elif defined( UNDER_CE ) + VLC_UNUSED( psz_file); VLC_UNUSED( i_line ); -#elif defined( ST_INIT_IN_ST_H ) - i_result = st_mutex_destroy( p_mutex->mutex ); + DeleteCriticalSection( &p_mutex->csection ); #elif defined( WIN32 ) - if( p_mutex->mutex ) - { - CloseHandle( p_mutex->mutex ); - } - else - { - DeleteCriticalSection( &p_mutex->csection ); - } - return 0; + VLC_UNUSED( psz_file); VLC_UNUSED( i_line ); -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) - i_result = pthread_mutex_destroy( &p_mutex->mutex ); - if ( i_result ) - { - i_thread = (int)pthread_self(); - psz_error = strerror(i_result); - } - -#elif defined( HAVE_CTHREADS_H ) - return 0; + CloseHandle( *p_mutex ); #elif defined( HAVE_KERNEL_SCHEDULER_H ) if( p_mutex->init == 9999 ) - { delete_sem( p_mutex->lock ); - } p_mutex->init = 0; - return B_OK; -#endif - if( i_result ) - { - msg_Err( p_mutex->p_this, - "thread %d: mutex_destroy failed at %s:%d (%d:%s)", - i_thread, psz_file, i_line, i_result, psz_error ); - } - return i_result; +#endif } /***************************************************************************** * vlc_cond_init: initialize a condition *****************************************************************************/ -int __vlc_cond_init( vlc_object_t *p_this, vlc_cond_t *p_condvar ) +int __vlc_cond_init( vlc_cond_t *p_condvar ) { - p_condvar->p_this = p_this; - -#if defined( PTH_INIT_IN_PTH_H ) - return pth_cond_init( &p_condvar->cond ); - -#elif defined( ST_INIT_IN_ST_H ) - p_condvar->cond = st_cond_new(); - return ( p_condvar->cond == NULL ) ? errno : 0; - -#elif defined( WIN32 ) - /* Initialize counter */ - p_condvar->i_waiting_threads = 0; - - /* Misc init */ - p_condvar->i_win9x_cv = p_this->p_vlc->i_win9x_cv; - p_condvar->SignalObjectAndWait = p_this->p_vlc->SignalObjectAndWait; - - if( p_this->p_vlc->i_win9x_cv == 0 ) - { - /* Create an auto-reset event. */ - p_condvar->event = CreateEvent( NULL, /* no security */ - FALSE, /* auto-reset event */ - FALSE, /* start non-signaled */ - NULL ); /* unnamed */ - - p_condvar->semaphore = NULL; - return !p_condvar->event; - } - else - { - p_condvar->semaphore = CreateSemaphore( NULL, /* no security */ - 0, /* initial count */ - 0x7fffffff, /* max count */ - NULL ); /* unnamed */ - - if( p_this->p_vlc->i_win9x_cv == 1 ) - /* Create a manual-reset event initially signaled. */ - p_condvar->event = CreateEvent( NULL, TRUE, TRUE, NULL ); - else - /* Create a auto-reset event. */ - p_condvar->event = CreateEvent( NULL, FALSE, FALSE, NULL ); - - InitializeCriticalSection( &p_condvar->csection ); - - return !p_condvar->semaphore || !p_condvar->event; - } - -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) - return pthread_cond_init( &p_condvar->cond, NULL ); - -#elif defined( HAVE_CTHREADS_H ) - /* condition_init() */ - spin_lock_init( &p_condvar->lock ); - cthread_queue_init( &p_condvar->queue ); - p_condvar->name = 0; - p_condvar->implications = 0; - - return 0; +#if defined( LIBVLC_USE_PTHREAD ) + pthread_condattr_t attr; + int ret; + + ret = pthread_condattr_init (&attr); + if (ret) + return ret; + +# if !defined (_POSIX_CLOCK_SELECTION) + /* Fairly outdated POSIX support (that was defined in 2001) */ +# define _POSIX_CLOCK_SELECTION (-1) +# endif +# if (_POSIX_CLOCK_SELECTION >= 0) + /* NOTE: This must be the same clock as the one in mtime.c */ + pthread_condattr_setclock (&attr, CLOCK_MONOTONIC); +# endif + + ret = pthread_cond_init (p_condvar, &attr); + pthread_condattr_destroy (&attr); + return ret; + +#elif defined( UNDER_CE ) || defined( WIN32 ) + /* Create an auto-reset event. */ + *p_condvar = CreateEvent( NULL, /* no security */ + FALSE, /* auto-reset event */ + FALSE, /* start non-signaled */ + NULL ); /* unnamed */ + return *p_condvar ? 0 : ENOMEM; #elif defined( HAVE_KERNEL_SCHEDULER_H ) if( !p_condvar ) @@ -443,253 +390,565 @@ int __vlc_cond_init( vlc_object_t *p_this, vlc_cond_t *p_condvar ) p_condvar->thread = -1; p_condvar->init = 9999; return 0; + #endif } /***************************************************************************** * vlc_cond_destroy: destroy a condition, inner version *****************************************************************************/ -int __vlc_cond_destroy( char * psz_file, int i_line, vlc_cond_t *p_condvar ) +void __vlc_cond_destroy( const char * psz_file, int i_line, vlc_cond_t *p_condvar ) { - int i_result; - /* In case of error : */ - int i_thread = -1; - const char * psz_error = ""; - -#if defined( PTH_INIT_IN_PTH_H ) - return 0; - -#elif defined( ST_INIT_IN_ST_H ) - i_result = st_cond_destroy( p_condvar->cond ); +#if defined( LIBVLC_USE_PTHREAD ) + int val = pthread_cond_destroy( p_condvar ); + VLC_THREAD_ASSERT ("destroying condition"); -#elif defined( WIN32 ) - if( !p_condvar->semaphore ) - i_result = !CloseHandle( p_condvar->event ); - else - i_result = !CloseHandle( p_condvar->event ) - || !CloseHandle( p_condvar->semaphore ); - -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) - i_result = pthread_cond_destroy( &p_condvar->cond ); - if ( i_result ) - { - i_thread = (int)pthread_self(); - psz_error = strerror(i_result); - } +#elif defined( UNDER_CE ) || defined( WIN32 ) + VLC_UNUSED( psz_file); VLC_UNUSED( i_line ); -#elif defined( HAVE_CTHREADS_H ) - return 0; + CloseHandle( *p_condvar ); #elif defined( HAVE_KERNEL_SCHEDULER_H ) p_condvar->init = 0; - return 0; -#endif - if( i_result ) - { - msg_Err( p_condvar->p_this, - "thread %d: cond_destroy failed at %s:%d (%d:%s)", - i_thread, psz_file, i_line, i_result, psz_error ); - } - return i_result; +#endif } /***************************************************************************** - * vlc_thread_create: create a thread, inner version - ***************************************************************************** - * Note that i_priority is only taken into account on platforms supporting - * userland real-time priority threads. + * vlc_tls_create: create a thread-local variable *****************************************************************************/ -int __vlc_thread_create( vlc_object_t *p_this, char * psz_file, int i_line, - char *psz_name, void * ( *func ) ( void * ), - int i_priority, vlc_bool_t b_wait ) +int vlc_threadvar_create( vlc_threadvar_t *p_tls, void (*destr) (void *) ) { int i_ret; - vlc_mutex_lock( &p_this->object_lock ); - -#ifdef GPROF - wrapper_t wrapper; - - /* Initialize the wrapper structure */ - wrapper.func = func; - wrapper.p_data = (void *)p_this; - getitimer( ITIMER_PROF, &wrapper.itimer ); - vlc_mutex_init( p_this, &wrapper.lock ); - vlc_cond_init( p_this, &wrapper.wait ); - vlc_mutex_lock( &wrapper.lock ); +#if defined( LIBVLC_USE_PTHREAD ) + i_ret = pthread_key_create( p_tls, destr ); +#elif defined( UNDER_CE ) + i_ret = ENOSYS; +#elif defined( WIN32 ) + /* FIXME: remember/use the destr() callback and stop leaking whatever */ + *p_tls = TlsAlloc(); + i_ret = (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0; +#else +# error Unimplemented! +#endif + return i_ret; +} - /* Alter user-passed data so that we call the wrapper instead - * of the real function */ - p_data = &wrapper; - func = vlc_thread_wrapper; +void vlc_threadvar_delete (vlc_threadvar_t *p_tls) +{ +#if defined( LIBVLC_USE_PTHREAD ) + pthread_key_delete (*p_tls); +#elif defined( UNDER_CE ) +#elif defined( WIN32 ) + TlsFree (*p_tls); +#else +# error Unimplemented! #endif +} -#if defined( PTH_INIT_IN_PTH_H ) - p_this->thread_id = pth_spawn( PTH_ATTR_DEFAULT, func, (void *)p_this ); - i_ret = 0; +#if defined (LIBVLC_USE_PTHREAD) +#elif defined (WIN32) +static unsigned __stdcall vlc_entry (void *data) +{ + vlc_thread_t self = data; + self->data = self->entry (self->data); + return 0; +} +#endif -#elif defined( ST_INIT_IN_ST_H ) - p_this->thread_id = st_thread_create( func, (void *)p_this, 1, 0 ); - i_ret = 0; - -#elif defined( WIN32 ) +/** + * Creates and starts new thread. + * + * @param p_handle [OUT] pointer to write the handle of the created thread to + * @param entry entry point for the thread + * @param data data parameter given to the entry point + * @param priority thread priority value + * @return 0 on success, a standard error code on error. + */ +int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data, + int priority) +{ + int ret; + +#if defined( LIBVLC_USE_PTHREAD ) + pthread_attr_t attr; + pthread_attr_init (&attr); + + /* Block the signals that signals interface plugin handles. + * If the LibVLC caller wants to handle some signals by itself, it should + * block these before whenever invoking LibVLC. And it must obviously not + * start the VLC signals interface plugin. + * + * LibVLC will normally ignore any interruption caused by an asynchronous + * signal during a system call. But there may well be some buggy cases + * where it fails to handle EINTR (bug reports welcome). Some underlying + * libraries might also not handle EINTR properly. + */ + sigset_t oldset; { - unsigned threadID; - /* When using the MSVCRT C library you have to use the _beginthreadex - * function instead of CreateThread, otherwise you'll end up with - * memory leaks and the signal functions not working */ - p_this->thread_id = - (HANDLE)_beginthreadex( NULL, 0, (PTHREAD_START) func, - (void *)p_this, 0, &threadID ); + sigset_t set; + sigemptyset (&set); + sigdelset (&set, SIGHUP); + sigaddset (&set, SIGINT); + sigaddset (&set, SIGQUIT); + sigaddset (&set, SIGTERM); + + sigaddset (&set, SIGPIPE); /* We don't want this one, really! */ + pthread_sigmask (SIG_BLOCK, &set, &oldset); } - - if ( p_this->thread_id && i_priority ) { - if ( !SetThreadPriority(p_this->thread_id, i_priority) ) - { - msg_Warn( p_this, "couldn't set a faster priority" ); - i_priority = 0; - } - } + struct sched_param sp = { .sched_priority = priority, }; + int policy; - i_ret = ( p_this->thread_id ? 0 : 1 ); + if (sp.sched_priority <= 0) + sp.sched_priority += sched_get_priority_max (policy = SCHED_OTHER); + else + sp.sched_priority += sched_get_priority_min (policy = SCHED_RR); -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) - i_ret = pthread_create( &p_this->thread_id, NULL, func, (void *)p_this ); + pthread_attr_setschedpolicy (&attr, policy); + pthread_attr_setschedparam (&attr, &sp); + } - if ( i_priority ) + ret = pthread_create (p_handle, &attr, entry, data); + pthread_sigmask (SIG_SETMASK, &oldset, NULL); + pthread_attr_destroy (&attr); + +#elif defined( WIN32 ) || defined( UNDER_CE ) + /* When using the MSVCRT C library you have to use the _beginthreadex + * function instead of CreateThread, otherwise you'll end up with + * memory leaks and the signal functions not working (see Microsoft + * Knowledge Base, article 104641) */ + HANDLE hThread; + vlc_thread_t th = malloc (sizeof (*p_handle)); + + if (th == NULL) + return ENOMEM; + + th->data = data; + th->entry = entry; +#if defined( UNDER_CE ) + hThread = CreateThread (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL); +#else + hThread = (HANDLE)(uintptr_t) + _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL); +#endif + + if (hThread) { - struct sched_param param; - memset( ¶m, 0, sizeof(struct sched_param) ); - param.sched_priority = i_priority; - if ( pthread_setschedparam( p_this->thread_id, SCHED_RR, ¶m ) ) + /* Thread closes the handle when exiting, duplicate it here + * to be on the safe side when joining. */ + if (!DuplicateHandle (GetCurrentProcess (), hThread, + GetCurrentProcess (), &th->handle, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { - msg_Warn( p_this, "couldn't go to real-time priority" ); - i_priority = 0; + CloseHandle (hThread); + free (th); + return ENOMEM; } + + ResumeThread (hThread); + if (priority) + SetThreadPriority (hThread, priority); + + ret = 0; + *p_handle = th; + } + else + { + ret = errno; + free (th); } -#elif defined( HAVE_CTHREADS_H ) - p_this->thread_id = cthread_fork( (cthread_fn_t)func, (any_t)p_this ); - i_ret = 0; +#elif defined( HAVE_KERNEL_SCHEDULER_H ) + *p_handle = spawn_thread( entry, psz_name, priority, data ); + ret = resume_thread( *p_handle ); + +#endif + return ret; +} + +#if defined (WIN32) +/* APC procedure for thread cancellation */ +static void CALLBACK vlc_cancel_self (ULONG_PTR dummy) +{ + (void)dummy; + vlc_control_cancel (VLC_DO_CANCEL); +} +#endif + +/** + * Marks a thread as cancelled. Next time the target thread reaches a + * cancellation point (while not having disabled cancellation), it will + * run its cancellation cleanup handler, the thread variable destructors, and + * terminate. vlc_join() must be used afterward regardless of a thread being + * cancelled or not. + */ +void vlc_cancel (vlc_thread_t thread_id) +{ +#if defined (LIBVLC_USE_PTHREAD) + pthread_cancel (thread_id); +#elif defined (WIN32) + QueueUserAPC (vlc_cancel_self, thread_id->handle, 0); +#endif +} + +/** + * Waits for a thread to complete (if needed), and destroys it. + * This is a cancellation point; in case of cancellation, the join does _not_ + * occur. + * + * @param handle thread handle + * @param p_result [OUT] pointer to write the thread return value or NULL + * @return 0 on success, a standard error code otherwise. + */ +int vlc_join (vlc_thread_t handle, void **result) +{ +#if defined( LIBVLC_USE_PTHREAD ) + return pthread_join (handle, result); + +#elif defined( UNDER_CE ) || defined( WIN32 ) + do + vlc_testcancel (); + while (WaitForSingleObjectEx (handle->handle, INFINITE, TRUE) + == WAIT_IO_COMPLETION); + + CloseHandle (handle->handle); + if (result) + *result = handle->data; + free (handle); + return 0; #elif defined( HAVE_KERNEL_SCHEDULER_H ) - p_this->thread_id = spawn_thread( (thread_func)func, psz_name, - B_NORMAL_PRIORITY, (void *)p_this ); - i_ret = resume_thread( p_this->thread_id ); + int32_t exit_value; + ret = (B_OK == wait_for_thread( p_priv->thread_id, &exit_value )); + if( !ret && result ) + *result = (void *)exit_value; + return ret; #endif +} -#ifdef GPROF - if( i_ret == 0 ) + +struct vlc_thread_boot +{ + void * (*entry) (vlc_object_t *); + vlc_object_t *object; +}; + +static void *thread_entry (void *data) +{ + vlc_object_t *obj = ((struct vlc_thread_boot *)data)->object; + void *(*func) (vlc_object_t *) = ((struct vlc_thread_boot *)data)->entry; + + free (data); +#ifndef NDEBUG + vlc_threadvar_set (&thread_object_key, obj); +#endif + msg_Dbg (obj, "thread started"); + func (obj); + msg_Dbg (obj, "thread ended"); + + return NULL; +} + +/***************************************************************************** + * vlc_thread_create: create a thread, inner version + ***************************************************************************** + * Note that i_priority is only taken into account on platforms supporting + * userland real-time priority threads. + *****************************************************************************/ +int __vlc_thread_create( vlc_object_t *p_this, const char * psz_file, int i_line, + const char *psz_name, void * ( *func ) ( vlc_object_t * ), + int i_priority, bool b_wait ) +{ + int i_ret; + vlc_object_internals_t *p_priv = vlc_internals( p_this ); + + struct vlc_thread_boot *boot = malloc (sizeof (*boot)); + if (boot == NULL) + return errno; + boot->entry = func; + boot->object = p_this; + + vlc_object_lock( p_this ); + + /* Make sure we don't re-create a thread if the object has already one */ + assert( !p_priv->b_thread ); + +#if defined( LIBVLC_USE_PTHREAD ) +#ifndef __APPLE__ + if( config_GetInt( p_this, "rt-priority" ) > 0 ) +#endif { - vlc_cond_wait( &wrapper.wait, &wrapper.lock ); + /* Hack to avoid error msg */ + if( config_GetType( p_this, "rt-offset" ) ) + i_priority += config_GetInt( p_this, "rt-offset" ); } - - vlc_mutex_unlock( &wrapper.lock ); - vlc_mutex_destroy( &wrapper.lock ); - vlc_cond_destroy( &wrapper.wait ); #endif + i_ret = vlc_clone( &p_priv->thread_id, thread_entry, boot, i_priority ); if( i_ret == 0 ) { if( b_wait ) { - msg_Dbg( p_this, "waiting for thread completion" ); - vlc_cond_wait( &p_this->object_wait, &p_this->object_lock ); + msg_Dbg( p_this, "waiting for thread initialization" ); + vlc_object_wait( p_this ); } - p_this->b_thread = 1; - - msg_Dbg( p_this, "thread %d (%s) created at priority %d (%s:%d)", - p_this->thread_id, psz_name, i_priority, + p_priv->b_thread = true; + msg_Dbg( p_this, "thread %lu (%s) created at priority %d (%s:%d)", + (unsigned long)p_priv->thread_id, psz_name, i_priority, psz_file, i_line ); - - vlc_mutex_unlock( &p_this->object_lock ); } else { - msg_Err( p_this, "%s thread could not be created at %s:%d (%s)", - psz_name, psz_file, i_line, strerror(i_ret) ); - vlc_mutex_unlock( &p_this->object_lock ); + errno = i_ret; + msg_Err( p_this, "%s thread could not be created at %s:%d (%m)", + psz_name, psz_file, i_line ); } + vlc_object_unlock( p_this ); return i_ret; } /***************************************************************************** - * vlc_thread_ready: tell the parent thread we were successfully spawned + * vlc_thread_set_priority: set the priority of the current thread when we + * couldn't set it in vlc_thread_create (for instance for the main thread) *****************************************************************************/ -void __vlc_thread_ready( vlc_object_t *p_this ) +int __vlc_thread_set_priority( vlc_object_t *p_this, const char * psz_file, + int i_line, int i_priority ) { - vlc_mutex_lock( &p_this->object_lock ); - vlc_cond_signal( &p_this->object_wait ); - vlc_mutex_unlock( &p_this->object_lock ); + vlc_object_internals_t *p_priv = vlc_internals( p_this ); + + if( !p_priv->b_thread ) + { + msg_Err( p_this, "couldn't set priority of non-existent thread" ); + return ESRCH; + } + +#if defined( LIBVLC_USE_PTHREAD ) +# ifndef __APPLE__ + if( config_GetInt( p_this, "rt-priority" ) > 0 ) +# endif + { + int i_error, i_policy; + struct sched_param param; + + memset( ¶m, 0, sizeof(struct sched_param) ); + if( config_GetType( p_this, "rt-offset" ) ) + i_priority += config_GetInt( p_this, "rt-offset" ); + if( i_priority <= 0 ) + { + param.sched_priority = (-1) * i_priority; + i_policy = SCHED_OTHER; + } + else + { + param.sched_priority = i_priority; + i_policy = SCHED_RR; + } + if( (i_error = pthread_setschedparam( p_priv->thread_id, + i_policy, ¶m )) ) + { + errno = i_error; + msg_Warn( p_this, "couldn't set thread priority (%s:%d): %m", + psz_file, i_line ); + i_priority = 0; + } + } + +#elif defined( WIN32 ) || defined( UNDER_CE ) + VLC_UNUSED( psz_file); VLC_UNUSED( i_line ); + + if( !SetThreadPriority(p_priv->thread_id->handle, i_priority) ) + { + msg_Warn( p_this, "couldn't set a faster priority" ); + return 1; + } + +#endif + + return 0; } /***************************************************************************** * vlc_thread_join: wait until a thread exits, inner version *****************************************************************************/ -void __vlc_thread_join( vlc_object_t *p_this, char * psz_file, int i_line ) +void __vlc_thread_join( vlc_object_t *p_this, const char * psz_file, int i_line ) { + vlc_object_internals_t *p_priv = vlc_internals( p_this ); int i_ret = 0; -#if defined( PTH_INIT_IN_PTH_H ) - i_ret = pth_join( p_this->thread_id, NULL ); - -#elif defined( ST_INIT_IN_ST_H ) - i_ret = st_thread_join( p_this->thread_id, NULL ); - -#elif defined( WIN32 ) - WaitForSingleObject( p_this->thread_id, INFINITE ); +#if defined( LIBVLC_USE_PTHREAD ) + /* Make sure we do return if we are calling vlc_thread_join() + * from the joined thread */ + if (pthread_equal (pthread_self (), p_priv->thread_id)) + { + msg_Warn (p_this, "joining the active thread (VLC might crash)"); + i_ret = pthread_detach (p_priv->thread_id); + } + else + i_ret = vlc_join (p_priv->thread_id, NULL); + +#elif defined( UNDER_CE ) || defined( WIN32 ) + HANDLE hThread; + FILETIME create_ft, exit_ft, kernel_ft, user_ft; + int64_t real_time, kernel_time, user_time; + + if( ! DuplicateHandle(GetCurrentProcess(), + p_priv->thread_id->handle, + GetCurrentProcess(), + &hThread, + 0, + FALSE, + DUPLICATE_SAME_ACCESS) ) + { + p_priv->b_thread = false; + i_ret = GetLastError(); + goto error; + } -#elif defined( PTHREAD_COND_T_IN_PTHREAD_H ) - i_ret = pthread_join( p_this->thread_id, NULL ); + vlc_join( p_priv->thread_id, NULL ); -#elif defined( HAVE_CTHREADS_H ) - cthread_join( p_this->thread_id ); - i_ret = 1; + if( GetThreadTimes( hThread, &create_ft, &exit_ft, &kernel_ft, &user_ft ) ) + { + real_time = + ((((int64_t)exit_ft.dwHighDateTime)<<32)| exit_ft.dwLowDateTime) - + ((((int64_t)create_ft.dwHighDateTime)<<32)| create_ft.dwLowDateTime); + real_time /= 10; + + kernel_time = + ((((int64_t)kernel_ft.dwHighDateTime)<<32)| + kernel_ft.dwLowDateTime) / 10; + + user_time = + ((((int64_t)user_ft.dwHighDateTime)<<32)| + user_ft.dwLowDateTime) / 10; + + msg_Dbg( p_this, "thread times: " + "real %"PRId64"m%fs, kernel %"PRId64"m%fs, user %"PRId64"m%fs", + real_time/60/1000000, + (double)((real_time%(60*1000000))/1000000.0), + kernel_time/60/1000000, + (double)((kernel_time%(60*1000000))/1000000.0), + user_time/60/1000000, + (double)((user_time%(60*1000000))/1000000.0) ); + } + CloseHandle( hThread ); +error: -#elif defined( HAVE_KERNEL_SCHEDULER_H ) - int32 exit_value; - wait_for_thread( p_this->thread_id, &exit_value ); +#else + i_ret = vlc_join( p_priv->thread_id, NULL ); #endif if( i_ret ) { - msg_Err( p_this, "thread_join(%d) failed at %s:%d (%s)", - p_this->thread_id, psz_file, i_line, strerror(i_ret) ); + errno = i_ret; + msg_Err( p_this, "thread_join(%lu) failed at %s:%d (%m)", + (unsigned long)p_priv->thread_id, psz_file, i_line ); } else - { - msg_Dbg( p_this, "thread %d joined (%s:%d)", - p_this->thread_id, psz_file, i_line ); - } + msg_Dbg( p_this, "thread %lu joined (%s:%d)", + (unsigned long)p_priv->thread_id, psz_file, i_line ); - p_this->b_thread = 0; + p_priv->b_thread = false; } -/***************************************************************************** - * vlc_thread_wrapper: wrapper around thread functions used when profiling. - *****************************************************************************/ -#ifdef GPROF -static void *vlc_thread_wrapper( void *p_wrapper ) +void vlc_thread_cancel (vlc_object_t *obj) { - /* Put user data in thread-local variables */ - void * p_data = ((wrapper_t*)p_wrapper)->p_data; - vlc_thread_func_t func = ((wrapper_t*)p_wrapper)->func; + vlc_object_internals_t *priv = vlc_internals (obj); - /* Set the profile timer value */ - setitimer( ITIMER_PROF, &((wrapper_t*)p_wrapper)->itimer, NULL ); + if (priv->b_thread) + vlc_cancel (priv->thread_id); +} - /* Tell the calling thread that we don't need its data anymore */ - vlc_mutex_lock( &((wrapper_t*)p_wrapper)->lock ); - vlc_cond_signal( &((wrapper_t*)p_wrapper)->wait ); - vlc_mutex_unlock( &((wrapper_t*)p_wrapper)->lock ); +#ifndef LIBVLC_USE_PTHREAD +typedef struct vlc_cancel_t +{ + vlc_cleanup_t *cleaners; + bool killable; + bool killed; +} vlc_cancel_t; +#endif - /* Call the real function */ - return func( p_data ); -} +void vlc_control_cancel (int cmd, ...) +{ + /* NOTE: This function only modifies thread-specific data, so there is no + * need to lock anything. */ +#ifdef LIBVLC_USE_PTHREAD + (void) cmd; + assert (0); +#else + va_list ap; + + va_start (ap, cmd); + + vlc_cancel_t *nfo = vlc_threadvar_get (&cancel_key); + if (nfo == NULL) + { + nfo = malloc (sizeof (*nfo)); + if (nfo == NULL) + abort (); + nfo->cleaners = NULL; + nfo->killed = false; + nfo->killable = true; + } + + switch (cmd) + { + case VLC_SAVE_CANCEL: + { + int *p_state = va_arg (ap, int *); + *p_state = nfo->killable; + nfo->killable = false; + break; + } + + case VLC_RESTORE_CANCEL: + { + int state = va_arg (ap, int); + nfo->killable = state != 0; + break; + } + + case VLC_TEST_CANCEL: + if (nfo->killable && nfo->killed) + { + for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next) + p->proc (p->data); + free (nfo); +#if defined (LIBVLC_USE_PTHREAD) + pthread_exit (PTHREAD_CANCELLED); +#elif defined (WIN32) + _endthread (); +#else +# error Not implemented! #endif + } + break; + + case VLC_DO_CANCEL: + nfo->killed = true; + break; + + case VLC_CLEANUP_PUSH: + { + /* cleaner is a pointer to the caller stack, no need to allocate + * and copy anything. As a nice side effect, this cannot fail. */ + vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *); + cleaner->next = nfo->cleaners; + nfo->cleaners = cleaner; + break; + } + + case VLC_CLEANUP_POP: + { + nfo->cleaners = nfo->cleaners->next; + break; + } + } + va_end (ap); +#endif +}