X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fmisc%2Fpthread.c;h=ca422cf69766f97eb341b5308137626d78c2d8fd;hb=84b88ba59e2940c45bb3625bb5b2085613b8faef;hp=9264598250729010573c3053f81cbdb244950e90;hpb=4dafeacf0814904f8adadd3875fec95c3e97dff7;p=vlc diff --git a/src/misc/pthread.c b/src/misc/pthread.c index 9264598250..ca422cf697 100644 --- a/src/misc/pthread.c +++ b/src/misc/pthread.c @@ -35,6 +35,7 @@ #include #include /* fsync() */ #include +#include #include #ifdef __linux__ @@ -88,6 +89,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 +120,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 @@ -139,17 +143,19 @@ void vlc_mutex_init( vlc_mutex_t *p_mutex ) { pthread_mutexattr_t attr; - if( pthread_mutexattr_init( &attr ) ) + if (unlikely(pthread_mutexattr_init (&attr))) abort(); -#ifndef NDEBUG +#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 - if( pthread_mutex_init( p_mutex, &attr ) ) + if (unlikely(pthread_mutex_init (p_mutex, &attr))) abort(); pthread_mutexattr_destroy( &attr ); } @@ -161,13 +167,14 @@ void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex ) { pthread_mutexattr_t attr; - 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 - if( pthread_mutex_init( p_mutex, &attr ) ) + if (unlikely(pthread_mutex_init (p_mutex, &attr))) abort(); pthread_mutexattr_destroy( &attr ); } @@ -249,14 +256,14 @@ void vlc_mutex_unlock (vlc_mutex_t *p_mutex) VLC_THREAD_ASSERT ("unlocking mutex"); } -/***************************************************************************** - * vlc_cond_init: initialize a condition variable - *****************************************************************************/ -void 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; - if (pthread_condattr_init (&attr)) + if (unlikely(pthread_condattr_init (&attr))) abort (); #if !defined (_POSIX_CLOCK_SELECTION) /* Fairly outdated POSIX support (that was defined in 2001) */ @@ -267,11 +274,22 @@ void vlc_cond_init( vlc_cond_t *p_condvar ) pthread_condattr_setclock (&attr, CLOCK_MONOTONIC); #endif - if (pthread_cond_init (p_condvar, &attr)) + if (unlikely(pthread_cond_init (p_condvar, &attr))) abort (); pthread_condattr_destroy (&attr); } +/** + * 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 (); +} + /** * Destroys a condition variable. No threads shall be waiting or signaling the * condition. @@ -344,7 +362,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, @@ -375,6 +397,103 @@ int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex, return val; } +/** + * Initializes a semaphore. + */ +void vlc_sem_init (vlc_sem_t *sem, unsigned value) +{ + if (unlikely(sem_init (sem, 0, value))) + abort (); +} + +/** + * Destroys a semaphore. + */ +void vlc_sem_destroy (vlc_sem_t *sem) +{ + if (likely(sem_destroy (sem) == 0)) + return; + + int val = errno; + 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) +{ + if (likely(sem_post (sem) == 0)) + return 0; + + int val = errno; + 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; + + do + if (likely(sem_wait (sem) == 0)) + return; + while ((val = errno) == EINTR); + + 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"); +} + /** * Allocates a thread-specific variable. * @param key where to store the thread-specific variable handle @@ -416,6 +535,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. * @@ -459,8 +603,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) @@ -513,13 +658,15 @@ void vlc_cancel (vlc_thread_t thread_id) } /** - * 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) { @@ -527,6 +674,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. @@ -558,7 +727,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 @@ -584,25 +753,70 @@ void vlc_control_cancel (int cmd, ...) assert (0); } -#ifndef HAVE_POSIX_TIMER -/* We have no fallback currently. We'll just crash on timer API usage. */ -static void timer_not_supported(void) + +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) { - fprintf(stderr, "*** Error: Timer API is not supported on this platform.\n"); - abort(); + 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; } -#endif -static void vlc_timer_do (union sigval val) +static void *vlc_timer_thread (void *data) { - vlc_timer_t *id = val.sival_ptr; - id->func (id->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, and - * a timer is only serialized against itself. + * @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 @@ -611,29 +825,21 @@ static void vlc_timer_do (union sigval val) */ int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data) { -#ifdef HAVE_POSIX_TIMER - struct sigevent ev; - - memset (&ev, 0, sizeof (ev)); - ev.sigev_notify = SIGEV_THREAD; - ev.sigev_value.sival_ptr = id; - ev.sigev_notify_function = vlc_timer_do; - ev.sigev_notify_attributes = NULL; - id->func = func; - id->data = data; - -#if (_POSIX_CLOCK_SELECTION >= 0) - if (timer_create (CLOCK_MONOTONIC, &ev, &id->handle)) -#else - if (timer_create (CLOCK_REALTIME, &ev, &id->handle)) -#endif - return errno; - - return 0; -#else - timer_not_supported(); + 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; -#endif } /** @@ -643,16 +849,19 @@ int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data) * @warning This function must be called before the timer data can be * freed and before the timer callback function can be unloaded. * - * @param timer to destroy + * @param timer timer to destroy */ -void vlc_timer_destroy (vlc_timer_t *id) +void vlc_timer_destroy (vlc_timer_t timer) { -#ifdef HAVE_POSIX_TIMER - int val = timer_delete (id->handle); - VLC_THREAD_ASSERT ("deleting timer"); -#else - timer_not_supported(); -#endif + 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); } /** @@ -664,7 +873,7 @@ void vlc_timer_destroy (vlc_timer_t *id) * the system is busy or suspended, or because a previous iteration of the * timer is still running. See also vlc_timer_getoverrun(). * - * @param id initialized timer pointer + * @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 @@ -672,51 +881,46 @@ void vlc_timer_destroy (vlc_timer_t *id) * @param interval zero to fire the timer just once, otherwise the timer * repetition interval. */ -void vlc_timer_schedule (vlc_timer_t *id, bool absolute, +void vlc_timer_schedule (vlc_timer_t timer, bool absolute, mtime_t value, mtime_t interval) { -#ifdef HAVE_POSIX_TIMER - lldiv_t vad = lldiv (value, CLOCK_FREQ); - lldiv_t itd = lldiv (interval, CLOCK_FREQ); - struct itimerspec it = { - .it_interval = { - .tv_sec = itd.quot, - .tv_nsec = (1000000000 / CLOCK_FREQ) * itd.rem, - }, - .it_value = { - .tv_sec = vad.quot, - .tv_nsec = (1000000000 / CLOCK_FREQ) * vad.rem, - }, - }; - int flags = absolute ? TIMER_ABSTIME : 0; - - int val = timer_settime (id->handle, flags, &it, NULL); - VLC_THREAD_ASSERT ("scheduling timer"); -#else - timer_not_supported(); -#endif + 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); } /** - * @param id initialized timer pointer + * 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 (const vlc_timer_t *id) +unsigned vlc_timer_getoverrun (vlc_timer_t timer) { -#ifdef HAVE_POSIX_TIMER - int val = timer_getoverrun (id->handle); -#ifndef NDEBUG - if (val == -1) - { - val = errno; - VLC_THREAD_ASSERT ("fetching timer overrun counter"); - } -#endif - return val; -#else - timer_not_supported(); - return 0; -#endif + unsigned ret; + + vlc_mutex_lock (&timer->lock); + ret = timer->overruns; + timer->overruns = 0; + vlc_mutex_unlock (&timer->lock); + return ret; }