X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fmisc%2Fpthread.c;h=ebbad780d4c8216759e6d1791a763b9da81a3f84;hb=314d91aaf1fcd5807caf3be7ab2c7da78751f837;hp=9391f05846f07ce82a91b8def544e6dbe961149c;hpb=78d87996ccb92d1dc91c9987685f976ed3be08a6;p=vlc diff --git a/src/misc/pthread.c b/src/misc/pthread.c index 9391f05846..ebbad780d4 100644 --- a/src/misc/pthread.c +++ b/src/misc/pthread.c @@ -35,6 +35,7 @@ #include #include /* fsync() */ #include +#include #include #ifdef __linux__ @@ -47,6 +48,7 @@ #ifdef __APPLE__ # include /* gettimeofday in vlc_cond_timedwait */ +# include /* mach_task_self in semaphores */ #endif /** @@ -88,6 +90,7 @@ static void vlc_thread_fatal (const char *action, int error, const char *function, const char *file, unsigned line) { + int canc = vlc_savecancel (); fprintf (stderr, "LibVLC fatal error %s (%d) in thread %lu ", action, error, vlc_threadid ()); vlc_trace (function, file, line); @@ -118,11 +121,13 @@ vlc_thread_fatal (const char *action, int error, #endif fflush (stderr); + vlc_restorecancel (canc); abort (); } # define VLC_THREAD_ASSERT( action ) \ - if (val) vlc_thread_fatal (action, val, __func__, __FILE__, __LINE__) + if (unlikely(val)) \ + vlc_thread_fatal (action, val, __func__, __FILE__, __LINE__) #else # define VLC_THREAD_ASSERT( action ) ((void)val) #endif @@ -135,43 +140,44 @@ int pthread_mutexattr_setkind_np( pthread_mutexattr_t *attr, int kind ); /***************************************************************************** * vlc_mutex_init: initialize a mutex *****************************************************************************/ -int vlc_mutex_init( vlc_mutex_t *p_mutex ) +void vlc_mutex_init( vlc_mutex_t *p_mutex ) { pthread_mutexattr_t attr; - int i_result; - pthread_mutexattr_init( &attr ); - -#ifndef NDEBUG + if (unlikely(pthread_mutexattr_init (&attr))) + abort(); +#ifdef NDEBUG + pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_NORMAL ); +#else /* Create error-checking mutex to detect problems more easily. */ -# if defined (__GLIBC__) && (__GLIBC_MINOR__ < 6) +# if defined (__GLIBC__) && (__GLIBC__ == 2) && (__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 ); + if (unlikely(pthread_mutex_init (p_mutex, &attr))) + abort(); pthread_mutexattr_destroy( &attr ); - return i_result; } /***************************************************************************** * vlc_mutex_init: initialize a recursive mutex (Do not use) *****************************************************************************/ -int vlc_mutex_init_recursive( vlc_mutex_t *p_mutex ) +void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex ) { pthread_mutexattr_t attr; - int i_result; - pthread_mutexattr_init( &attr ); -#if defined (__GLIBC__) && (__GLIBC_MINOR__ < 6) + if (unlikely(pthread_mutexattr_init (&attr))) + abort(); +#if defined (__GLIBC__) && (__GLIBC__ == 2) && (__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 ); + if (unlikely(pthread_mutex_init (p_mutex, &attr))) + abort(); pthread_mutexattr_destroy( &attr ); - return( i_result ); } @@ -251,18 +257,15 @@ void vlc_mutex_unlock (vlc_mutex_t *p_mutex) VLC_THREAD_ASSERT ("unlocking mutex"); } -/***************************************************************************** - * vlc_cond_init: initialize a condition variable - *****************************************************************************/ -int vlc_cond_init( vlc_cond_t *p_condvar ) +/** + * Initializes a condition variable. + */ +void vlc_cond_init (vlc_cond_t *p_condvar) { pthread_condattr_t attr; - int ret; - - ret = pthread_condattr_init (&attr); - if (ret) - return ret; + if (unlikely(pthread_condattr_init (&attr))) + abort (); #if !defined (_POSIX_CLOCK_SELECTION) /* Fairly outdated POSIX support (that was defined in 2001) */ # define _POSIX_CLOCK_SELECTION (-1) @@ -272,9 +275,20 @@ int vlc_cond_init( vlc_cond_t *p_condvar ) pthread_condattr_setclock (&attr, CLOCK_MONOTONIC); #endif - ret = pthread_cond_init (p_condvar, &attr); + if (unlikely(pthread_cond_init (p_condvar, &attr))) + abort (); pthread_condattr_destroy (&attr); - return ret; +} + +/** + * Initializes a condition variable. + * Contrary to vlc_cond_init(), the wall clock will be used as a reference for + * the vlc_cond_timedwait() time-out parameter. + */ +void vlc_cond_init_daytime (vlc_cond_t *p_condvar) +{ + if (unlikely(pthread_cond_init (p_condvar, NULL))) + abort (); } /** @@ -349,7 +363,11 @@ void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex) /** * Waits for a condition variable up to a certain date. - * This works like vlc_cond_wait(), except for the additional timeout. + * This works like vlc_cond_wait(), except for the additional time-out. + * + * If the variable was initialized with vlc_cond_init(), the timeout has the + * same arbitrary origin as mdate(). If the variable was initialized with + * vlc_cond_init_daytime(), the timeout is expressed from the Unix epoch. * * @param p_condvar condition variable to wait on * @param p_mutex mutex which is unlocked while waiting, @@ -361,23 +379,158 @@ void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex) int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex, mtime_t deadline) { -#ifdef __APPLE__ - /* mdate() is mac_absolute_time on OSX, which we must convert to do - * the same base than gettimeofday() which pthread_cond_timedwait - * relies on. */ - mtime_t oldbase = mdate(); - struct timeval tv; - gettimeofday(&tv, NULL); - mtime_t newbase = (mtime_t)tv.tv_sec * 1000000 + (mtime_t) tv.tv_usec; - deadline = deadline - oldbase + newbase; -#endif +#if defined(__APPLE__) && !defined(__powerpc__) && !defined( __ppc__ ) && !defined( __ppc64__ ) + /* mdate() is the monotonic clock, timedwait origin is gettimeofday() which + * isn't monotonic. Use imedwait_relative_np() instead + */ + mtime_t base = mdate(); + deadline -= base; + if (deadline < 0) + deadline = 0; lldiv_t d = lldiv( deadline, CLOCK_FREQ ); struct timespec ts = { d.quot, d.rem * (1000000000 / CLOCK_FREQ) }; + int val = pthread_cond_timedwait_relative_np(p_condvar, p_mutex, &ts); + if (val != ETIMEDOUT) + VLC_THREAD_ASSERT ("timed-waiting on condition"); + return val; +#else + lldiv_t d = lldiv( deadline, CLOCK_FREQ ); + struct timespec ts = { d.quot, d.rem * (1000000000 / CLOCK_FREQ) }; int val = pthread_cond_timedwait (p_condvar, p_mutex, &ts); if (val != ETIMEDOUT) VLC_THREAD_ASSERT ("timed-waiting on condition"); return val; +#endif +} + +/** + * Initializes a semaphore. + */ +void vlc_sem_init (vlc_sem_t *sem, unsigned value) +{ +#if defined(__APPLE__) + if (unlikely(semaphore_create(mach_task_self(), sem, SYNC_POLICY_FIFO, value) != KERN_SUCCESS)) + abort (); +#else + if (unlikely(sem_init (sem, 0, value))) + abort (); +#endif +} + +/** + * Destroys a semaphore. + */ +void vlc_sem_destroy (vlc_sem_t *sem) +{ + int val; + +#if defined(__APPLE__) + if (likely(semaphore_destroy(mach_task_self(), *sem) == KERN_SUCCESS)) + return; + + val = EINVAL; +#else + if (likely(sem_destroy (sem) == 0)) + return; + + val = errno; +#endif + + VLC_THREAD_ASSERT ("destroying semaphore"); +} + +/** + * Increments the value of a semaphore. + * @return 0 on success, EOVERFLOW in case of integer overflow + */ +int vlc_sem_post (vlc_sem_t *sem) +{ + int val; + +#if defined(__APPLE__) + if (likely(semaphore_signal(*sem) == KERN_SUCCESS)) + return 0; + + val = EINVAL; +#else + if (likely(sem_post (sem) == 0)) + return 0; + + val = errno; +#endif + + if (unlikely(val != EOVERFLOW)) + VLC_THREAD_ASSERT ("unlocking semaphore"); + return val; +} + +/** + * Atomically wait for the semaphore to become non-zero (if needed), + * then decrements it. + */ +void vlc_sem_wait (vlc_sem_t *sem) +{ + int val; + +#if defined(__APPLE__) + if (likely(semaphore_wait(*sem) == KERN_SUCCESS)) + return; + + val = EINVAL; +#else + do + if (likely(sem_wait (sem) == 0)) + return; + while ((val = errno) == EINTR); +#endif + + VLC_THREAD_ASSERT ("locking semaphore"); +} + +/** + * Initializes a read/write lock. + */ +void vlc_rwlock_init (vlc_rwlock_t *lock) +{ + if (unlikely(pthread_rwlock_init (lock, NULL))) + abort (); +} + +/** + * Destroys an initialized unused read/write lock. + */ +void vlc_rwlock_destroy (vlc_rwlock_t *lock) +{ + int val = pthread_rwlock_destroy (lock); + VLC_THREAD_ASSERT ("destroying R/W lock"); +} + +/** + * Acquires a read/write lock for reading. Recursion is allowed. + */ +void vlc_rwlock_rdlock (vlc_rwlock_t *lock) +{ + int val = pthread_rwlock_rdlock (lock); + VLC_THREAD_ASSERT ("acquiring R/W lock for reading"); +} + +/** + * Acquires a read/write lock for writing. Recursion is not allowed. + */ +void vlc_rwlock_wrlock (vlc_rwlock_t *lock) +{ + int val = pthread_rwlock_wrlock (lock); + VLC_THREAD_ASSERT ("acquiring R/W lock for writing"); +} + +/** + * Releases a read/write lock. + */ +void vlc_rwlock_unlock (vlc_rwlock_t *lock) +{ + int val = pthread_rwlock_unlock (lock); + VLC_THREAD_ASSERT ("releasing R/W lock"); } /** @@ -421,6 +574,31 @@ void *vlc_threadvar_get (vlc_threadvar_t key) return pthread_getspecific (key); } +static bool rt_priorities = false; +static int rt_offset; + +void vlc_threads_setup (libvlc_int_t *p_libvlc) +{ + static vlc_mutex_t lock = VLC_STATIC_MUTEX; + static bool initialized = false; + + vlc_mutex_lock (&lock); + /* Initializes real-time priorities before any thread is created, + * just once per process. */ + if (!initialized) + { +#ifndef __APPLE__ + if (var_InheritBool (p_libvlc, "rt-priority")) +#endif + { + rt_offset = var_InheritInteger (p_libvlc, "rt-offset"); + rt_priorities = true; + } + initialized = true; + } + vlc_mutex_unlock (&lock); +} + /** * Creates and starts new thread. * @@ -464,8 +642,9 @@ int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data, #if defined (_POSIX_PRIORITY_SCHEDULING) && (_POSIX_PRIORITY_SCHEDULING >= 0) \ && defined (_POSIX_THREAD_PRIORITY_SCHEDULING) \ && (_POSIX_THREAD_PRIORITY_SCHEDULING >= 0) + if (rt_priorities) { - struct sched_param sp = { .sched_priority = priority, }; + struct sched_param sp = { .sched_priority = priority + rt_offset, }; int policy; if (sp.sched_priority <= 0) @@ -515,16 +694,21 @@ int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data, void vlc_cancel (vlc_thread_t thread_id) { pthread_cancel (thread_id); +#ifdef HAVE_MAEMO + pthread_kill (thread_id, SIGRTMIN); +#endif } /** - * Waits for a thread to complete (if needed), and destroys it. + * Waits for a thread to complete (if needed), then destroys it. * This is a cancellation point; in case of cancellation, the join does _not_ * occur. + * @warning + * A thread cannot join itself (normally VLC will abort if this is attempted). + * Also, a detached thread cannot be joined. * * @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) { @@ -532,6 +716,28 @@ void vlc_join (vlc_thread_t handle, void **result) VLC_THREAD_ASSERT ("joining thread"); } +/** + * Detaches a thread. When the specified thread completes, it will be + * automatically destroyed (in particular, its stack will be reclaimed), + * instead of waiting for another thread to call vlc_join(). If the thread has + * already completed, it will be destroyed immediately. + * + * When a thread performs some work asynchronously and may complete much + * earlier than it can be joined, detaching the thread can save memory. + * However, care must be taken that any resources used by a detached thread + * remains valid until the thread completes. This will typically involve some + * kind of thread-safe signaling. + * + * A thread may detach itself. + * + * @param handle thread handle + */ +void vlc_detach (vlc_thread_t handle) +{ + int val = pthread_detach (handle); + VLC_THREAD_ASSERT ("detaching thread"); +} + /** * Save the current cancellation state (enabled or disabled), then disable * cancellation for the calling thread. @@ -563,7 +769,7 @@ void vlc_restorecancel (int state) /* This should fail if an invalid value for given for state */ VLC_THREAD_ASSERT ("restoring cancellation"); - if (oldstate != PTHREAD_CANCEL_DISABLE) + if (unlikely(oldstate != PTHREAD_CANCEL_DISABLE)) vlc_thread_fatal ("restoring cancellation while not disabled", EINVAL, __func__, __FILE__, __LINE__); #else @@ -588,3 +794,175 @@ void vlc_control_cancel (int cmd, ...) (void) cmd; assert (0); } + + +struct vlc_timer +{ + vlc_thread_t thread; + vlc_mutex_t lock; + vlc_cond_t wait; + void (*func) (void *); + void *data; + mtime_t value, interval; + unsigned users; + unsigned overruns; +}; + +static void *vlc_timer_do (void *data) +{ + struct vlc_timer *timer = data; + + timer->func (timer->data); + + vlc_mutex_lock (&timer->lock); + assert (timer->users > 0); + if (--timer->users == 0) + vlc_cond_signal (&timer->wait); + vlc_mutex_unlock (&timer->lock); + return NULL; +} + +static void *vlc_timer_thread (void *data) +{ + struct vlc_timer *timer = data; + mtime_t value, interval; + + vlc_mutex_lock (&timer->lock); + value = timer->value; + interval = timer->interval; + vlc_mutex_unlock (&timer->lock); + + for (;;) + { + vlc_thread_t th; + + mwait (value); + + vlc_mutex_lock (&timer->lock); + if (vlc_clone (&th, vlc_timer_do, timer, VLC_THREAD_PRIORITY_INPUT)) + timer->overruns++; + else + { + vlc_detach (th); + timer->users++; + } + vlc_mutex_unlock (&timer->lock); + + if (interval == 0) + return NULL; + + value += interval; + } +} + +/** + * Initializes an asynchronous timer. + * @warning Asynchronous timers are processed from an unspecified thread. + * Also, multiple occurences of an interval timer can run concurrently. + * + * @param id pointer to timer to be initialized + * @param func function that the timer will call + * @param data parameter for the timer function + * @return 0 on success, a system error code otherwise. + */ +int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data) +{ + struct vlc_timer *timer = malloc (sizeof (*timer)); + + if (unlikely(timer == NULL)) + return ENOMEM; + vlc_mutex_init (&timer->lock); + vlc_cond_init (&timer->wait); + assert (func); + timer->func = func; + timer->data = data; + timer->value = 0; + timer->interval = 0; + timer->users = 0; + timer->overruns = 0; + *id = timer; + return 0; +} + +/** + * Destroys an initialized timer. If needed, the timer is first disarmed. + * This function is undefined if the specified timer is not initialized. + * + * @warning This function must be called before the timer data can be + * freed and before the timer callback function can be unloaded. + * + * @param timer timer to destroy + */ +void vlc_timer_destroy (vlc_timer_t timer) +{ + vlc_timer_schedule (timer, false, 0, 0); + vlc_mutex_lock (&timer->lock); + while (timer->users != 0) + vlc_cond_wait (&timer->wait, &timer->lock); + vlc_mutex_unlock (&timer->lock); + + vlc_cond_destroy (&timer->wait); + vlc_mutex_destroy (&timer->lock); + free (timer); +} + +/** + * Arm or disarm an initialized timer. + * This functions overrides any previous call to itself. + * + * @note A timer can fire later than requested due to system scheduling + * limitations. An interval timer can fail to trigger sometimes, either because + * the system is busy or suspended, or because a previous iteration of the + * timer is still running. See also vlc_timer_getoverrun(). + * + * @param timer initialized timer + * @param absolute the timer value origin is the same as mdate() if true, + * the timer value is relative to now if false. + * @param value zero to disarm the timer, otherwise the initial time to wait + * before firing the timer. + * @param interval zero to fire the timer just once, otherwise the timer + * repetition interval. + */ +void vlc_timer_schedule (vlc_timer_t timer, bool absolute, + mtime_t value, mtime_t interval) +{ + static vlc_mutex_t lock = VLC_STATIC_MUTEX; + + vlc_mutex_lock (&lock); + vlc_mutex_lock (&timer->lock); + if (timer->value) + { + vlc_mutex_unlock (&timer->lock); + vlc_cancel (timer->thread); + vlc_join (timer->thread, NULL); + vlc_mutex_lock (&timer->lock); + timer->value = 0; + } + if ((value != 0) + && (vlc_clone (&timer->thread, vlc_timer_thread, timer, + VLC_THREAD_PRIORITY_INPUT) == 0)) + { + timer->value = (absolute ? 0 : mdate ()) + value; + timer->interval = interval; + } + vlc_mutex_unlock (&timer->lock); + vlc_mutex_unlock (&lock); +} + +/** + * Fetch and reset the overrun counter for a timer. + * @param timer initialized timer + * @return the timer overrun counter, i.e. the number of times that the timer + * should have run but did not since the last actual run. If all is well, this + * is zero. + */ +unsigned vlc_timer_getoverrun (vlc_timer_t timer) +{ + unsigned ret; + + vlc_mutex_lock (&timer->lock); + ret = timer->overruns; + timer->overruns = 0; + vlc_mutex_unlock (&timer->lock); + return ret; +}