X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fmisc%2Fthreads.c;h=e35d6b5fab3328068d82be82ff333f756cf98320;hb=0597403277c49d3fc23da19fb6f6954e984bae86;hp=dd36dfef907544f71542e1c6f27d8e15141751a2;hpb=2ea23dde42e48a07d3ea541cbe366c89f9a5cb64;p=vlc diff --git a/src/misc/threads.c b/src/misc/threads.c index dd36dfef90..e35d6b5fab 100644 --- a/src/misc/threads.c +++ b/src/misc/threads.c @@ -1,13 +1,14 @@ /***************************************************************************** * threads.c : threads implementation for the VideoLAN client ***************************************************************************** - * Copyright (C) 1999-2007 the VideoLAN team + * Copyright (C) 1999-2008 the VideoLAN team * $Id$ * * Authors: Jean-Marc Dressler * Samuel Hocevar * Gildas Bazin * Clément Sténac + * Rémi Denis-Courmont * * 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 @@ -31,84 +32,76 @@ #include #include "libvlc.h" +#include #include #ifdef HAVE_UNISTD_H # include #endif #include -#define VLC_THREADS_UNINITIALIZED 0 -#define VLC_THREADS_PENDING 1 -#define VLC_THREADS_ERROR 2 -#define VLC_THREADS_READY 3 - -/***************************************************************************** - * Global mutex for lazy initialization of the threads system - *****************************************************************************/ -static volatile unsigned i_initializations = 0; - #if defined( LIBVLC_USE_PTHREAD ) # include - -static pthread_mutex_t once_mutex = PTHREAD_MUTEX_INITIALIZER; +#else +static vlc_threadvar_t cancel_key; #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; -} +#ifdef HAVE_EXECINFO_H +# include +#endif -#ifndef NDEBUG /** - * Object running the current thread + * Print a backtrace to the standard error for debugging purpose. */ -static vlc_threadvar_t thread_object_key; - -vlc_object_t *vlc_threadobj (void) +void vlc_trace (const char *fn, const char *file, unsigned line) { - return vlc_threadvar_get (&thread_object_key); -} + fprintf (stderr, "at %s:%u in %s\n", file, line, fn); + fflush (stderr); /* needed before switch to low-level I/O */ +#ifdef HAVE_BACKTRACE + void *stack[20]; + int len = backtrace (stack, sizeof (stack) / sizeof (stack[0])); + backtrace_symbols_fd (stack, len, 2); #endif +#ifndef WIN32 + fsync (2); +#endif +} -vlc_threadvar_t msg_context_global_key; - -#if defined(LIBVLC_USE_PTHREAD) static inline unsigned long vlc_threadid (void) { +#if defined(LIBVLC_USE_PTHREAD) 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 +#elif defined (WIN32) + return GetCurrentThreadId (); + +#else + return 0; + #endif +} /***************************************************************************** * vlc_thread_fatal: Report an error from the threading layer ***************************************************************************** * This is mostly meant for debugging. *****************************************************************************/ -void vlc_pthread_fatal (const char *action, int error, - const char *file, unsigned line) +static void +vlc_thread_fatal (const char *action, int error, + const char *function, 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); + fprintf (stderr, "LibVLC fatal error %s (%d) in thread %lu ", + action, error, vlc_threadid ()); + vlc_trace (function, file, line); /* 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 + fprintf (stderr, " Error message: %m\n"); +#elif !defined (WIN32) char buf[1000]; const char *msg; @@ -128,95 +121,139 @@ void vlc_pthread_fatal (const char *action, int error, #endif fflush (stderr); -#ifdef HAVE_BACKTRACE - void *stack[20]; - int len = backtrace (stack, sizeof (stack) / sizeof (stack[0])); - backtrace_symbols_fd (stack, len, 2); -#endif - abort (); } + +#ifndef NDEBUG +# define VLC_THREAD_ASSERT( action ) \ + if (val) vlc_thread_fatal (action, val, __func__, __FILE__, __LINE__) #else -void vlc_pthread_fatal (const char *action, int error, - const char *file, unsigned line) -{ - (void)action; (void)error; (void)file; (void)line; - abort(); -} +# define VLC_THREAD_ASSERT( action ) ((void)val) #endif -/***************************************************************************** - * vlc_threads_init: initialize threads system - ***************************************************************************** - * This function requires lazy initialization of a global lock in order to - * keep the library really thread-safe. Some architectures don't support this - * and thus do not guarantee the complete reentrancy. - *****************************************************************************/ -int vlc_threads_init( void ) +/** + * Per-thread cancellation data + */ +#ifndef LIBVLC_USE_PTHREAD_CANCEL +typedef struct vlc_cancel_t { - int i_ret = VLC_SUCCESS; + vlc_cleanup_t *cleaners; + bool killable; + bool killed; +# ifdef UNDER_CE + HANDLE cancel_event; +# endif +} vlc_cancel_t; - /* 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 ); +# ifndef UNDER_CE +# define VLC_CANCEL_INIT { NULL, true, false } +# else +# define VLC_CANCEL_INIT { NULL, true, false, NULL } +# endif #endif - if( i_initializations == 0 ) +#ifdef UNDER_CE +static void CALLBACK vlc_cancel_self (ULONG_PTR dummy); + +static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles, + DWORD delay) +{ + vlc_cancel_t *nfo = vlc_threadvar_get (&cancel_key); + if (nfo == NULL) { - p_root = vlc_custom_create( (vlc_object_t *)NULL, sizeof( *p_root ), - VLC_OBJECT_GENERIC, "root" ); - if( p_root == NULL ) - { - i_ret = VLC_ENOMEM; - goto out; - } + /* Main thread - cannot be cancelled anyway */ + return WaitForMultipleObjects (count, handles, FALSE, delay); + } + HANDLE new_handles[count + 1]; + memcpy(new_handles, handles, count * sizeof(HANDLE)); + new_handles[count] = nfo->cancel_event; + DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE, + delay); + if (result == WAIT_OBJECT_0 + count) + { + vlc_cancel_self (NULL); + return WAIT_IO_COMPLETION; + } + else + { + return result; + } +} - /* 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 ); +DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable) +{ + if (bAlertable) + { + DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds); + return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION; + } + else + { + Sleep(dwMilliseconds); + return 0; } - i_initializations++; +} -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 ); +DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds, + BOOL bAlertable) +{ + if (bAlertable) + { + /* The MSDN documentation specifies different return codes, + * but in practice they are the same. We just check that it + * remains so. */ +#if WAIT_ABANDONED != WAIT_ABANDONED_0 +# error Windows headers changed, code needs to be rewritten! #endif - - return i_ret; + return vlc_cancelable_wait (1, &hHandle, dwMilliseconds); + } + else + { + return WaitForSingleObject (hHandle, dwMilliseconds); + } } -/***************************************************************************** - * vlc_threads_end: stop threads system - ***************************************************************************** - * FIXME: This function is far from being threadsafe. - *****************************************************************************/ -void vlc_threads_end( void ) +DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles, + BOOL bWaitAll, DWORD dwMilliseconds, + BOOL bAlertable) { -#if defined( LIBVLC_USE_PTHREAD ) - pthread_mutex_lock( &once_mutex ); + if (bAlertable) + { + /* We do not support the bWaitAll case */ + assert (! bWaitAll); + return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds); + } + else + { + return WaitForMultipleObjects (nCount, lpHandles, bWaitAll, + dwMilliseconds); + } +} #endif - assert( i_initializations > 0 ); +#ifdef WIN32 +static vlc_mutex_t super_mutex; + +BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved) +{ + (void) hinstDll; + (void) lpvReserved; - if( i_initializations == 1 ) + switch (fdwReason) { - vlc_object_release( p_root ); - vlc_threadvar_delete( &msg_context_global_key ); -#ifndef NDEBUG - vlc_threadvar_delete( &thread_object_key ); -#endif - } - i_initializations--; + case DLL_PROCESS_ATTACH: + vlc_mutex_init (&super_mutex); + vlc_threadvar_create (&cancel_key, free); + break; -#if defined( LIBVLC_USE_PTHREAD ) - pthread_mutex_unlock( &once_mutex ); -#endif + case DLL_PROCESS_DETACH: + vlc_threadvar_delete( &cancel_key ); + vlc_mutex_destroy (&super_mutex); + break; + } + return TRUE; } +#endif #if defined (__GLIBC__) && (__GLIBC_MINOR__ < 6) /* This is not prototyped under glibc, though it exists. */ @@ -245,34 +282,13 @@ int vlc_mutex_init( vlc_mutex_t *p_mutex ) 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 ) - *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 */ - if( p_mutex == NULL ) - { - return B_BAD_VALUE; - } - - if( p_mutex->init == 9999 ) - { - return EALREADY; - } - - p_mutex->lock = create_sem( 1, "BeMutex" ); - if( p_mutex->lock < B_NO_ERROR ) - { - return( -1 ); - } - - p_mutex->init = 9999; - return B_OK; + /* This creates a recursive mutex. This is OK as fast mutexes have + * no defined behavior in case of recursive locking. */ + InitializeCriticalSection (&p_mutex->mutex); + p_mutex->initialized = 1; + return 0; #endif } @@ -295,48 +311,88 @@ int vlc_mutex_init_recursive( vlc_mutex_t *p_mutex ) 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! + InitializeCriticalSection( &p_mutex->mutex ); + p_mutex->initialized = 1; + return 0; + #endif } -/***************************************************************************** - * vlc_mutex_destroy: destroy a mutex, inner version - *****************************************************************************/ -void __vlc_mutex_destroy( const char * psz_file, int i_line, vlc_mutex_t *p_mutex ) +/** + * Destroys a mutex. The mutex must not be locked. + * + * @param p_mutex mutex to destroy + * @return always succeeds + */ +void vlc_mutex_destroy (vlc_mutex_t *p_mutex) { #if defined( LIBVLC_USE_PTHREAD ) int val = pthread_mutex_destroy( p_mutex ); VLC_THREAD_ASSERT ("destroying mutex"); -#elif defined( UNDER_CE ) - VLC_UNUSED( psz_file); VLC_UNUSED( i_line ); +#elif defined( WIN32 ) + assert (InterlockedExchange (&p_mutex->initialized, -1) == 1); + DeleteCriticalSection (&p_mutex->mutex); - DeleteCriticalSection( &p_mutex->csection ); +#endif +} + +/** + * Acquires a mutex. If needed, waits for any other thread to release it. + * Beware of deadlocks when locking multiple mutexes at the same time, + * or when using mutexes from callbacks. + * + * @param p_mutex mutex initialized with vlc_mutex_init() or + * vlc_mutex_init_recursive() + */ +void vlc_mutex_lock (vlc_mutex_t *p_mutex) +{ +#if defined(LIBVLC_USE_PTHREAD) + int val = pthread_mutex_lock( p_mutex ); + VLC_THREAD_ASSERT ("locking mutex"); #elif defined( WIN32 ) - VLC_UNUSED( psz_file); VLC_UNUSED( i_line ); + if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0) + { /* ^^ We could also lock super_mutex all the time... sluggish */ + assert (p_mutex != &super_mutex); /* this one cannot be static */ + + vlc_mutex_lock (&super_mutex); + if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0) + vlc_mutex_init (p_mutex); + /* FIXME: destroy the mutex some time... */ + vlc_mutex_unlock (&super_mutex); + } + assert (InterlockedExchange (&p_mutex->initialized, 1) == 1); + EnterCriticalSection (&p_mutex->mutex); + +#endif +} - CloseHandle( *p_mutex ); -#elif defined( HAVE_KERNEL_SCHEDULER_H ) - if( p_mutex->init == 9999 ) - delete_sem( p_mutex->lock ); +/** + * Releases a mutex (or crashes if the mutex is not locked by the caller). + * @param p_mutex mutex locked with vlc_mutex_lock(). + */ +void vlc_mutex_unlock (vlc_mutex_t *p_mutex) +{ +#if defined(LIBVLC_USE_PTHREAD) + int val = pthread_mutex_unlock( p_mutex ); + VLC_THREAD_ASSERT ("unlocking mutex"); - p_mutex->init = 0; +#elif defined( WIN32 ) + assert (InterlockedExchange (&p_mutex->initialized, 1) == 1); + LeaveCriticalSection (&p_mutex->mutex); #endif } /***************************************************************************** - * vlc_cond_init: initialize a condition + * vlc_cond_init: initialize a condition variable *****************************************************************************/ -int __vlc_cond_init( vlc_cond_t *p_condvar ) +int vlc_cond_init( vlc_cond_t *p_condvar ) { #if defined( LIBVLC_USE_PTHREAD ) pthread_condattr_t attr; @@ -359,51 +415,168 @@ int __vlc_cond_init( vlc_cond_t *p_condvar ) pthread_condattr_destroy (&attr); return ret; -#elif defined( UNDER_CE ) || defined( WIN32 ) - /* Initialize counter */ - p_condvar->i_waiting_threads = 0; - - /* Create an auto-reset event. */ - p_condvar->event = CreateEvent( NULL, /* no security */ - FALSE, /* auto-reset event */ - FALSE, /* start non-signaled */ - NULL ); /* unnamed */ - return !p_condvar->event; - -#elif defined( HAVE_KERNEL_SCHEDULER_H ) - if( !p_condvar ) - { - return B_BAD_VALUE; - } +#elif defined( WIN32 ) + /* Create a manual-reset event (manual reset is needed for broadcast). */ + *p_condvar = CreateEvent( NULL, TRUE, FALSE, NULL ); + return *p_condvar ? 0 : ENOMEM; + +#endif +} + +/** + * Destroys a condition variable. No threads shall be waiting or signaling the + * condition. + * @param p_condvar condition variable to destroy + */ +void vlc_cond_destroy (vlc_cond_t *p_condvar) +{ +#if defined( LIBVLC_USE_PTHREAD ) + int val = pthread_cond_destroy( p_condvar ); + VLC_THREAD_ASSERT ("destroying condition"); + +#elif defined( WIN32 ) + CloseHandle( *p_condvar ); + +#endif +} - if( p_condvar->init == 9999 ) +/** + * Wakes up one thread waiting on a condition variable, if any. + * @param p_condvar condition variable + */ +void vlc_cond_signal (vlc_cond_t *p_condvar) +{ +#if defined(LIBVLC_USE_PTHREAD) + int val = pthread_cond_signal( p_condvar ); + VLC_THREAD_ASSERT ("signaling condition variable"); + +#elif defined( WIN32 ) + /* NOTE: This will cause a broadcast, that is wrong. + * This will also wake up the next waiting thread if no thread are yet + * waiting, which is also wrong. However both of these issues are allowed + * by the provision for spurious wakeups. Better have too many wakeups + * than too few (= deadlocks). */ + SetEvent (*p_condvar); + +#endif +} + +/** + * Wakes up all threads (if any) waiting on a condition variable. + * @param p_cond condition variable + */ +void vlc_cond_broadcast (vlc_cond_t *p_condvar) +{ +#if defined (LIBVLC_USE_PTHREAD) + pthread_cond_broadcast (p_condvar); + +#elif defined (WIN32) + SetEvent (*p_condvar); + +#endif +} + +/** + * Waits for a condition variable. The calling thread will be suspended until + * another thread calls vlc_cond_signal() or vlc_cond_broadcast() on the same + * condition variable, the thread is cancelled with vlc_cancel(), or the + * system causes a "spurious" unsolicited wake-up. + * + * A mutex is needed to wait on a condition variable. It must not be + * a recursive mutex. Although it is possible to use the same mutex for + * multiple condition, it is not valid to use different mutexes for the same + * condition variable at the same time from different threads. + * + * In case of thread cancellation, the mutex is always locked before + * cancellation proceeds. + * + * The canonical way to use a condition variable to wait for event foobar is: + @code + vlc_mutex_lock (&lock); + mutex_cleanup_push (&lock); // release the mutex in case of cancellation + + while (!foobar) + vlc_cond_wait (&wait, &lock); + + --- foobar is now true, do something about it here -- + + vlc_cleanup_run (); // release the mutex + @endcode + * + * @param p_condvar condition variable to wait on + * @param p_mutex mutex which is unlocked while waiting, + * then locked again when waking up. + * @param deadline absolute timeout + * + * @return 0 if the condition was signaled, an error code in case of timeout. + */ +void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex) +{ +#if defined(LIBVLC_USE_PTHREAD) + int val = pthread_cond_wait( p_condvar, p_mutex ); + VLC_THREAD_ASSERT ("waiting on condition"); + +#elif defined( WIN32 ) + DWORD result; + + do { - return EALREADY; + vlc_testcancel (); + LeaveCriticalSection (&p_mutex->mutex); + result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE); + EnterCriticalSection (&p_mutex->mutex); } + while (result == WAIT_IO_COMPLETION); - p_condvar->thread = -1; - p_condvar->init = 9999; - return 0; + ResetEvent (*p_condvar); #endif } -/***************************************************************************** - * vlc_cond_destroy: destroy a condition, inner version - *****************************************************************************/ -void __vlc_cond_destroy( const char * psz_file, int i_line, vlc_cond_t *p_condvar ) +/** + * Waits for a condition variable up to a certain date. + * This works like vlc_cond_wait(), except for the additional timeout. + * + * @param p_condvar condition variable to wait on + * @param p_mutex mutex which is unlocked while waiting, + * then locked again when waking up. + * @param deadline absolute timeout + * + * @return 0 if the condition was signaled, an error code in case of timeout. + */ +int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex, + mtime_t deadline) { -#if defined( LIBVLC_USE_PTHREAD ) - int val = pthread_cond_destroy( p_condvar ); - VLC_THREAD_ASSERT ("destroying condition"); +#if defined(LIBVLC_USE_PTHREAD) + lldiv_t d = lldiv( deadline, CLOCK_FREQ ); + struct timespec ts = { d.quot, d.rem * (1000000000 / CLOCK_FREQ) }; -#elif defined( UNDER_CE ) || defined( WIN32 ) - VLC_UNUSED( psz_file); VLC_UNUSED( i_line ); + int val = pthread_cond_timedwait (p_condvar, p_mutex, &ts); + if (val != ETIMEDOUT) + VLC_THREAD_ASSERT ("timed-waiting on condition"); + return val; - CloseHandle( p_condvar->event ); +#elif defined( WIN32 ) + DWORD result; + + do + { + vlc_testcancel (); -#elif defined( HAVE_KERNEL_SCHEDULER_H ) - p_condvar->init = 0; + mtime_t total = (deadline - mdate ())/1000; + if( total < 0 ) + total = 0; + + DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total; + LeaveCriticalSection (&p_mutex->mutex); + result = WaitForSingleObjectEx (*p_condvar, delay, TRUE); + EnterCriticalSection (&p_mutex->mutex); + } + while (result == WAIT_IO_COMPLETION); + + ResetEvent (*p_condvar); + + return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT; #endif } @@ -417,9 +590,8 @@ int vlc_threadvar_create( vlc_threadvar_t *p_tls, void (*destr) (void *) ) #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 @@ -432,7 +604,6 @@ 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 @@ -440,73 +611,35 @@ void vlc_threadvar_delete (vlc_threadvar_t *p_tls) #endif } -struct vlc_thread_boot -{ - void * (*entry) (void *); - vlc_object_t *object; -}; - #if defined (LIBVLC_USE_PTHREAD) -# define THREAD_RTYPE void * -# define THREAD_RVAL NULL #elif defined (WIN32) -# define THREAD_RTYPE __stdcall unsigned -# define THREAD_RVAL 0 -#endif - -static THREAD_RTYPE thread_entry (void *data) +static unsigned __stdcall vlc_entry (void *data) { - vlc_object_t *obj = ((struct vlc_thread_boot *)data)->object; - void *(*func) (void *) = ((struct vlc_thread_boot *)data)->entry; - - free (data); -#ifndef NDEBUG - vlc_threadvar_set (&thread_object_key, obj); + vlc_cancel_t cancel_data = VLC_CANCEL_INIT; + vlc_thread_t self = data; +#ifdef UNDER_CE + cancel_data.cancel_event = self->cancel_event; #endif - msg_Dbg (obj, "thread started"); - func (obj); - msg_Dbg (obj, "thread ended"); - libvlc_priv_t *libpriv = libvlc_priv (obj->p_libvlc); - vlc_mutex_lock (&libpriv->threads_lock); -#ifndef NDEBUG - libpriv->threads_count--; -#else - if (--libpriv->threads_count == 0) -#endif - vlc_cond_signal (&libpriv->threads_wait); - vlc_mutex_unlock (&libpriv->threads_lock); - return THREAD_RVAL; + vlc_threadvar_set (&cancel_key, &cancel_data); + self->data = self->entry (self->data); + return 0; } +#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. - *****************************************************************************/ -int __vlc_thread_create( vlc_object_t *p_this, const char * psz_file, int i_line, - const char *psz_name, void * ( *func ) ( void * ), - int i_priority, bool b_wait ) +/** + * 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 i_ret; - vlc_object_internals_t *p_priv = vlc_internals( p_this ); - libvlc_priv_t *libpriv = libvlc_priv (p_this->p_libvlc); - - struct vlc_thread_boot *boot = malloc (sizeof (*boot)); - if (boot == NULL) - return errno; - boot->entry = func; - boot->object = p_this; - - vlc_mutex_lock (&libpriv->threads_lock); - libpriv->threads_count++; - vlc_mutex_unlock (&libpriv->threads_lock); - - 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 ); + int ret; #if defined( LIBVLC_USE_PTHREAD ) pthread_attr_t attr; @@ -522,36 +655,51 @@ int __vlc_thread_create( vlc_object_t *p_this, const char * psz_file, int i_line * where it fails to handle EINTR (bug reports welcome). Some underlying * libraries might also not handle EINTR properly. */ - sigset_t set, oldset; - 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); - -#ifndef __APPLE__ - if( config_GetInt( p_this, "rt-priority" ) > 0 ) -#endif + sigset_t oldset; + { + 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); + } { - struct sched_param p = { .sched_priority = i_priority, }; + struct sched_param sp = { .sched_priority = priority, }; int policy; - /* Hack to avoid error msg */ - if( config_GetType( p_this, "rt-offset" ) ) - p.sched_priority += config_GetInt( p_this, "rt-offset" ); - if( p.sched_priority <= 0 ) - p.sched_priority += sched_get_priority_max (policy = SCHED_OTHER); + if (sp.sched_priority <= 0) + sp.sched_priority += sched_get_priority_max (policy = SCHED_OTHER); else - p.sched_priority += sched_get_priority_min (policy = SCHED_RR); + sp.sched_priority += sched_get_priority_min (policy = SCHED_RR); pthread_attr_setschedpolicy (&attr, policy); - pthread_attr_setschedparam (&attr, &p); + pthread_attr_setschedparam (&attr, &sp); } - i_ret = pthread_create( &p_priv->thread_id, &attr, thread_entry, boot ); + /* The thread stack size. + * The lower the value, the less address space per thread, the highest + * maximum simultaneous threads per process. Too low values will cause + * stack overflows and weird crashes. Set with caution. Also keep in mind + * that 64-bits platforms consume more stack than 32-bits one. + * + * Thanks to on-demand paging, thread stack size only affects address space + * consumption. In terms of memory, threads only use what they need + * (rounded up to the page boundary). + * + * For example, on Linux i386, the default is 2 mega-bytes, which supports + * about 320 threads per processes. */ +#define VLC_STACKSIZE (128 * sizeof (void *) * 1024) + +#ifdef VLC_STACKSIZE + ret = pthread_attr_setstacksize (&attr, VLC_STACKSIZE); + assert (ret == 0); /* fails iif VLC_STACKSIZE is invalid */ +#endif + + ret = pthread_create (p_handle, &attr, entry, data); pthread_sigmask (SIG_SETMASK, &oldset, NULL); pthread_attr_destroy (&attr); @@ -560,63 +708,190 @@ int __vlc_thread_create( vlc_object_t *p_this, const char * psz_file, int i_line * 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 (*th)); + + if (th == NULL) + return ENOMEM; + + th->data = data; + th->entry = entry; #if defined( UNDER_CE ) - HANDLE hThread = CreateThread( NULL, 0, thread_entry, - (LPVOID)boot, CREATE_SUSPENDED, NULL ); + th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL); + if (th->cancel_event == NULL) + { + free(th); + return errno; + } + hThread = CreateThread (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL); #else - HANDLE hThread = (HANDLE)(uintptr_t) - _beginthreadex( NULL, 0, thread_entry, boot, CREATE_SUSPENDED, NULL ); + hThread = (HANDLE)(uintptr_t) + _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL); #endif - if( hThread ) + + if (hThread) { - p_priv->thread_id = hThread; - ResumeThread (hThread); - i_ret = 0; - if( i_priority && !SetThreadPriority (hThread, i_priority) ) +#ifndef UNDER_CE + /* 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 set a faster priority" ); - i_priority = 0; + CloseHandle (hThread); + free (th); + return ENOMEM; } +#else + th->handle = hThread; +#endif + + ResumeThread (hThread); + if (priority) + SetThreadPriority (hThread, priority); + + ret = 0; + *p_handle = th; } else - i_ret = errno; + { + ret = errno; + free (th); + } -#elif defined( HAVE_KERNEL_SCHEDULER_H ) - p_priv->thread_id = spawn_thread( (thread_func)thread_entry, psz_name, - i_priority, p_data ); - i_ret = resume_thread( p_priv->thread_id ); +#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 - if( i_ret == 0 ) - { - if( b_wait ) - { - msg_Dbg( p_this, "waiting for thread completion" ); - vlc_object_wait( p_this ); - } +/** + * 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_CANCEL) + pthread_cancel (thread_id); +#elif defined (UNDER_CE) + SetEvent (thread_id->cancel_event); +#elif defined (WIN32) + QueueUserAPC (vlc_cancel_self, thread_id->handle, 0); +#else +# warning vlc_cancel is not implemented! +#endif +} - 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 ); +/** + * 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. + */ +void vlc_join (vlc_thread_t handle, void **result) +{ +#if defined( LIBVLC_USE_PTHREAD ) + int val = pthread_join (handle, result); + if (val) + vlc_thread_fatal ("joining thread", val, __func__, __FILE__, __LINE__); + +#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; +#if defined( UNDER_CE ) + CloseHandle (handle->cancel_event); +#endif + free (handle); + +#endif +} + + +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); + msg_Dbg (obj, "thread started"); + func (obj); + msg_Dbg (obj, "thread ended"); + + return NULL; +} + +#undef vlc_thread_create +/***************************************************************************** + * vlc_thread_create: create a thread + ***************************************************************************** + * 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 ) +{ + 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; + + /* 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 + { + /* Hack to avoid error msg */ + if( config_GetType( p_this, "rt-offset" ) ) + i_priority += config_GetInt( p_this, "rt-offset" ); } +#endif + + p_priv->b_thread = true; + i_ret = vlc_clone( &p_priv->thread_id, thread_entry, boot, i_priority ); + if( i_ret == 0 ) + msg_Dbg( p_this, "thread (%s) created at priority %d (%s:%d)", + psz_name, i_priority, psz_file, i_line ); else { + p_priv->b_thread = false; 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 ); - - if (i_ret) - { - vlc_mutex_lock (&libpriv->threads_lock); - if (--libpriv->threads_count == 0) - vlc_cond_signal (&libpriv->threads_wait); - vlc_mutex_unlock (&libpriv->threads_lock); - } return i_ret; } @@ -669,7 +944,7 @@ int __vlc_thread_set_priority( vlc_object_t *p_this, const char * psz_file, #elif defined( WIN32 ) || defined( UNDER_CE ) VLC_UNUSED( psz_file); VLC_UNUSED( i_line ); - if( !SetThreadPriority(p_priv->thread_id, i_priority) ) + if( !SetThreadPriority(p_priv->thread_id->handle, i_priority) ) { msg_Warn( p_this, "couldn't set a faster priority" ); return 1; @@ -683,36 +958,21 @@ int __vlc_thread_set_priority( vlc_object_t *p_this, const char * psz_file, /***************************************************************************** * vlc_thread_join: wait until a thread exits, inner version *****************************************************************************/ -void __vlc_thread_join( vlc_object_t *p_this, const char * psz_file, int i_line ) +void __vlc_thread_join( vlc_object_t *p_this ) { vlc_object_internals_t *p_priv = vlc_internals( p_this ); - int i_ret = 0; #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 = pthread_join (p_priv->thread_id, NULL); + vlc_join (p_priv->thread_id, NULL); #elif defined( UNDER_CE ) || defined( WIN32 ) - HMODULE hmodule; - BOOL (WINAPI *OurGetThreadTimes)( HANDLE, FILETIME*, FILETIME*, - FILETIME*, FILETIME* ); + HANDLE hThread; FILETIME create_ft, exit_ft, kernel_ft, user_ft; int64_t real_time, kernel_time, user_time; - HANDLE hThread; - /* - ** object will close its thread handle when destroyed, duplicate it here - ** to be on the safe side - */ +#ifndef UNDER_CE if( ! DuplicateHandle(GetCurrentProcess(), - p_priv->thread_id, + p_priv->thread_id->handle, GetCurrentProcess(), &hThread, 0, @@ -720,24 +980,15 @@ void __vlc_thread_join( vlc_object_t *p_this, const char * psz_file, int i_line DUPLICATE_SAME_ACCESS) ) { p_priv->b_thread = false; - i_ret = GetLastError(); - goto error; + return; /* We have a problem! */ } - - WaitForSingleObject( hThread, INFINITE ); - -#if defined( UNDER_CE ) - hmodule = GetModuleHandle( _T("COREDLL") ); #else - hmodule = GetModuleHandle( _T("KERNEL32") ); + hThread = p_priv->thread_id->handle; #endif - OurGetThreadTimes = (BOOL (WINAPI*)( HANDLE, FILETIME*, FILETIME*, - FILETIME*, FILETIME* )) - GetProcAddress( hmodule, _T("GetThreadTimes") ); - if( OurGetThreadTimes && - OurGetThreadTimes( hThread, - &create_ft, &exit_ft, &kernel_ft, &user_ft ) ) + vlc_join( p_priv->thread_id, NULL ); + + if( GetThreadTimes( hThread, &create_ft, &exit_ft, &kernel_ft, &user_ft ) ) { real_time = ((((int64_t)exit_ft.dwHighDateTime)<<32)| exit_ft.dwLowDateTime) - @@ -762,23 +1013,106 @@ void __vlc_thread_join( vlc_object_t *p_this, const char * psz_file, int i_line (double)((user_time%(60*1000000))/1000000.0) ); } CloseHandle( hThread ); -error: -#elif defined( HAVE_KERNEL_SCHEDULER_H ) - int32_t exit_value; - i_ret = (B_OK == wait_for_thread( p_priv->thread_id, &exit_value )); +#else + vlc_join( p_priv->thread_id, NULL ); #endif - if( i_ret ) + p_priv->b_thread = false; +} + +void vlc_thread_cancel (vlc_object_t *obj) +{ + vlc_object_internals_t *priv = vlc_internals (obj); + + if (priv->b_thread) + vlc_cancel (priv->thread_id); +} + +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_CANCEL + (void) cmd; + assert (0); +#else + va_list ap; + + vlc_cancel_t *nfo = vlc_threadvar_get (&cancel_key); + if (nfo == NULL) { - 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 ); +#ifdef WIN32 + /* Main thread - cannot be cancelled anyway */ + return; +#else + nfo = malloc (sizeof (*nfo)); + if (nfo == NULL) + return; /* Uho! Expect problems! */ + *nfo = VLC_CANCEL_INIT; + vlc_threadvar_set (&cancel_key, nfo); +#endif } - else - msg_Dbg( p_this, "thread %lu joined (%s:%d)", - (unsigned long)p_priv->thread_id, psz_file, i_line ); - p_priv->b_thread = false; + va_start (ap, cmd); + 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); +#ifndef WIN32 + free (nfo); +#endif +#if defined (LIBVLC_USE_PTHREAD) + pthread_exit (PTHREAD_CANCELLED); +#elif defined (UNDER_CE) + ExitThread(0); +#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 }