/***************************************************************************** * thread.c : pthread back-end for LibVLC ***************************************************************************** * Copyright (C) 1999-2009 VLC authors and VideoLAN * * 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 Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "libvlc.h" #include #include #include #include #include #include #include /* fsync() */ #include #include #ifdef HAVE_EXECINFO_H # include #endif #if defined(__SunOS) # include # include #endif #if !defined (_POSIX_TIMERS) # define _POSIX_TIMERS (-1) #endif #if !defined (_POSIX_CLOCK_SELECTION) /* Clock selection was defined in 2001 and became mandatory in 2008. */ # define _POSIX_CLOCK_SELECTION (-1) #endif #if !defined (_POSIX_MONOTONIC_CLOCK) # define _POSIX_MONOTONIC_CLOCK (-1) #endif #if (_POSIX_TIMERS > 0) static unsigned vlc_clock_prec; # if (_POSIX_MONOTONIC_CLOCK > 0) && (_POSIX_CLOCK_SELECTION > 0) /* Compile-time POSIX monotonic clock support */ # define vlc_clock_id (CLOCK_MONOTONIC) # elif (_POSIX_MONOTONIC_CLOCK == 0) && (_POSIX_CLOCK_SELECTION > 0) /* Run-time POSIX monotonic clock support (see clock_setup() below) */ static clockid_t vlc_clock_id; # else /* No POSIX monotonic clock support */ # define vlc_clock_id (CLOCK_REALTIME) # warning Monotonic clock not available. Expect timing issues. # endif /* _POSIX_MONOTONIC_CLOKC */ static void vlc_clock_setup_once (void) { # if (_POSIX_MONOTONIC_CLOCK == 0) long val = sysconf (_SC_MONOTONIC_CLOCK); assert (val != 0); vlc_clock_id = (val < 0) ? CLOCK_REALTIME : CLOCK_MONOTONIC; # endif struct timespec res; if (unlikely(clock_getres (vlc_clock_id, &res) != 0 || res.tv_sec != 0)) abort (); vlc_clock_prec = (res.tv_nsec + 500) / 1000; } static pthread_once_t vlc_clock_once = PTHREAD_ONCE_INIT; # define vlc_clock_setup() \ pthread_once(&vlc_clock_once, vlc_clock_setup_once) #else /* _POSIX_TIMERS */ # include /* gettimeofday() */ # define vlc_clock_setup() (void)0 # warning Monotonic clock not available. Expect timing issues. #endif /* _POSIX_TIMERS */ static struct timespec mtime_to_ts (vlc_tick_t date) { lldiv_t d = lldiv (date, CLOCK_FREQ); struct timespec ts = { d.quot, d.rem * (1000000000 / CLOCK_FREQ) }; return ts; } /** * Print a backtrace to the standard error for debugging purpose. */ void vlc_trace (const char *fn, const char *file, unsigned line) { 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 fsync (2); } #ifndef NDEBUG /** * Reports a fatal error from the threading layer, for debugging purposes. */ 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_thread_id ()); vlc_trace (function, file, line); perror ("Thread error"); fflush (stderr); vlc_restorecancel (canc); abort (); } # define VLC_THREAD_ASSERT( action ) \ if (unlikely(val)) \ vlc_thread_fatal (action, val, __func__, __FILE__, __LINE__) #else # define VLC_THREAD_ASSERT( action ) ((void)val) #endif void vlc_mutex_init( vlc_mutex_t *p_mutex ) { pthread_mutexattr_t attr; if (unlikely(pthread_mutexattr_init (&attr))) abort(); #ifdef NDEBUG pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_DEFAULT); #else pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_ERRORCHECK); #endif if (unlikely(pthread_mutex_init (p_mutex, &attr))) abort(); pthread_mutexattr_destroy( &attr ); } void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex ) { pthread_mutexattr_t attr; if (unlikely(pthread_mutexattr_init (&attr))) abort(); pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE); if (unlikely(pthread_mutex_init (p_mutex, &attr))) abort(); pthread_mutexattr_destroy( &attr ); } void vlc_mutex_destroy (vlc_mutex_t *p_mutex) { int val = pthread_mutex_destroy( p_mutex ); VLC_THREAD_ASSERT ("destroying mutex"); } #ifndef NDEBUG # ifdef HAVE_VALGRIND_VALGRIND_H # include # else # define RUNNING_ON_VALGRIND (0) # endif /** * Asserts that a mutex is locked by the calling thread. */ void vlc_assert_locked (vlc_mutex_t *p_mutex) { if (RUNNING_ON_VALGRIND > 0) return; assert (pthread_mutex_lock (p_mutex) == EDEADLK); } #endif void vlc_mutex_lock (vlc_mutex_t *p_mutex) { int val = pthread_mutex_lock( p_mutex ); VLC_THREAD_ASSERT ("locking mutex"); } int vlc_mutex_trylock (vlc_mutex_t *p_mutex) { int val = pthread_mutex_trylock( p_mutex ); if (val != EBUSY) VLC_THREAD_ASSERT ("locking mutex"); return val; } void vlc_mutex_unlock (vlc_mutex_t *p_mutex) { int val = pthread_mutex_unlock( p_mutex ); VLC_THREAD_ASSERT ("unlocking mutex"); } void vlc_cond_init (vlc_cond_t *p_condvar) { pthread_condattr_t attr; if (unlikely(pthread_condattr_init (&attr))) abort (); #if (_POSIX_CLOCK_SELECTION > 0) vlc_clock_setup (); pthread_condattr_setclock (&attr, vlc_clock_id); #endif if (unlikely(pthread_cond_init (p_condvar, &attr))) abort (); pthread_condattr_destroy (&attr); } void vlc_cond_init_daytime (vlc_cond_t *p_condvar) { if (unlikely(pthread_cond_init (p_condvar, NULL))) abort (); } void vlc_cond_destroy (vlc_cond_t *p_condvar) { int val = pthread_cond_destroy( p_condvar ); VLC_THREAD_ASSERT ("destroying condition"); } void vlc_cond_signal (vlc_cond_t *p_condvar) { int val = pthread_cond_signal( p_condvar ); VLC_THREAD_ASSERT ("signaling condition variable"); } void vlc_cond_broadcast (vlc_cond_t *p_condvar) { pthread_cond_broadcast (p_condvar); } void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex) { int val = pthread_cond_wait( p_condvar, p_mutex ); VLC_THREAD_ASSERT ("waiting on condition"); } int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex, vlc_tick_t deadline) { struct timespec ts = mtime_to_ts (deadline); int val = pthread_cond_timedwait (p_condvar, p_mutex, &ts); if (val != ETIMEDOUT) VLC_THREAD_ASSERT ("timed-waiting on condition"); return val; } int vlc_cond_timedwait_daytime (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex, time_t deadline) { struct timespec ts = { deadline, 0 }; int val = pthread_cond_timedwait (p_condvar, p_mutex, &ts); if (val != ETIMEDOUT) VLC_THREAD_ASSERT ("timed-waiting on condition"); return val; } void vlc_sem_init (vlc_sem_t *sem, unsigned value) { if (unlikely(sem_init (sem, 0, value))) abort (); } void vlc_sem_destroy (vlc_sem_t *sem) { int val; if (likely(sem_destroy (sem) == 0)) return; val = errno; VLC_THREAD_ASSERT ("destroying semaphore"); } int vlc_sem_post (vlc_sem_t *sem) { int val; if (likely(sem_post (sem) == 0)) return 0; val = errno; if (unlikely(val != EOVERFLOW)) VLC_THREAD_ASSERT ("unlocking semaphore"); return val; } 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"); } void vlc_rwlock_init (vlc_rwlock_t *lock) { if (unlikely(pthread_rwlock_init (lock, NULL))) abort (); } void vlc_rwlock_destroy (vlc_rwlock_t *lock) { int val = pthread_rwlock_destroy (lock); VLC_THREAD_ASSERT ("destroying R/W lock"); } void vlc_rwlock_rdlock (vlc_rwlock_t *lock) { int val = pthread_rwlock_rdlock (lock); VLC_THREAD_ASSERT ("acquiring R/W lock for reading"); } void vlc_rwlock_wrlock (vlc_rwlock_t *lock) { int val = pthread_rwlock_wrlock (lock); VLC_THREAD_ASSERT ("acquiring R/W lock for writing"); } void vlc_rwlock_unlock (vlc_rwlock_t *lock) { int val = pthread_rwlock_unlock (lock); VLC_THREAD_ASSERT ("releasing R/W lock"); } int vlc_threadvar_create (vlc_threadvar_t *key, void (*destr) (void *)) { return pthread_key_create (key, destr); } void vlc_threadvar_delete (vlc_threadvar_t *p_tls) { pthread_key_delete (*p_tls); } int vlc_threadvar_set (vlc_threadvar_t key, void *value) { return pthread_setspecific (key, value); } 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) { if (var_InheritBool (p_libvlc, "rt-priority")) { rt_offset = var_InheritInteger (p_libvlc, "rt-offset"); rt_priorities = true; } initialized = true; } vlc_mutex_unlock (&lock); } static int vlc_clone_attr (vlc_thread_t *th, pthread_attr_t *attr, void *(*entry) (void *), void *data, int priority) { int ret; /* 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; { 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 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 + rt_offset, }; int policy; 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); pthread_attr_setschedpolicy (attr, policy); pthread_attr_setschedparam (attr, &sp); pthread_attr_setinheritsched (attr, PTHREAD_EXPLICIT_SCHED); } #else (void) priority; #endif /* 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(&th->handle, attr, entry, data); pthread_sigmask (SIG_SETMASK, &oldset, NULL); pthread_attr_destroy (attr); return ret; } int vlc_clone (vlc_thread_t *th, void *(*entry) (void *), void *data, int priority) { pthread_attr_t attr; pthread_attr_init (&attr); return vlc_clone_attr (th, &attr, entry, data, priority); } void vlc_join(vlc_thread_t th, void **result) { int val = pthread_join(th.handle, result); VLC_THREAD_ASSERT ("joining thread"); } /** * Creates and starts new detached thread. * A detached thread cannot be joined. Its resources will be automatically * released whenever the thread exits (in particular, its call stack will be * reclaimed). * * Detached thread are particularly useful when some work needs to be done * asynchronously, that is likely to be completed much earlier than the thread * can practically be joined. In this case, thread detach can spare memory. * * A detached thread may be cancelled, so as to expedite its termination. * Be extremely careful if you do this: while a normal joinable thread can * safely be cancelled after it has already exited, cancelling an already * exited detached thread is undefined: The thread handle would is destroyed * immediately when the detached thread exits. So you need to ensure that the * detached thread is still running before cancellation is attempted. * * @warning Care must be taken that any resources used by the detached thread * remains valid until the thread completes. * * @note A detached thread must eventually exit just like another other * thread. In practice, LibVLC will wait for detached threads to exit before * it unloads the plugins. * * @param th [OUT] pointer to hold the thread handle, or NULL * @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_detach (vlc_thread_t *th, void *(*entry) (void *), void *data, int priority) { vlc_thread_t dummy; pthread_attr_t attr; if (th == NULL) th = &dummy; pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); return vlc_clone_attr (th, &attr, entry, data, priority); } vlc_thread_t vlc_thread_self (void) { vlc_thread_t thread = { pthread_self() }; return thread; } #if !defined (__linux__) unsigned long vlc_thread_id (void) { return -1; } #endif int vlc_set_priority (vlc_thread_t th, int priority) { #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 + rt_offset, }; int policy; 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); if (pthread_setschedparam(th.handle, policy, &sp)) return VLC_EGENERIC; } #else (void) th; (void) priority; #endif return VLC_SUCCESS; } void vlc_cancel(vlc_thread_t th) { pthread_cancel(th.handle); } int vlc_savecancel (void) { int state; int val = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state); VLC_THREAD_ASSERT ("saving cancellation"); return state; } void vlc_restorecancel (int state) { #ifndef NDEBUG int oldstate, val; val = pthread_setcancelstate (state, &oldstate); /* This should fail if an invalid value for given for state */ VLC_THREAD_ASSERT ("restoring cancellation"); if (unlikely(oldstate != PTHREAD_CANCEL_DISABLE)) vlc_thread_fatal ("restoring cancellation while not disabled", EINVAL, __func__, __FILE__, __LINE__); #else pthread_setcancelstate (state, NULL); #endif } void vlc_testcancel (void) { pthread_testcancel (); } void vlc_control_cancel (int cmd, ...) { (void) cmd; vlc_assert_unreachable (); } vlc_tick_t mdate (void) { #if (_POSIX_TIMERS > 0) struct timespec ts; vlc_clock_setup (); if (unlikely(clock_gettime (vlc_clock_id, &ts) != 0)) abort (); return (INT64_C(1000000) * ts.tv_sec) + (ts.tv_nsec / 1000); #else struct timeval tv; if (unlikely(gettimeofday (&tv, NULL) != 0)) abort (); return (INT64_C(1000000) * tv.tv_sec) + tv.tv_usec; #endif } #undef mwait void mwait (vlc_tick_t deadline) { #if (_POSIX_CLOCK_SELECTION > 0) vlc_clock_setup (); /* If the deadline is already elapsed, or within the clock precision, * do not even bother the system timer. */ deadline -= vlc_clock_prec; struct timespec ts = mtime_to_ts (deadline); while (clock_nanosleep (vlc_clock_id, TIMER_ABSTIME, &ts, NULL) == EINTR); #else deadline -= mdate (); if (deadline > 0) msleep (deadline); #endif } #undef msleep void msleep (vlc_tick_t delay) { struct timespec ts = mtime_to_ts (delay); #if (_POSIX_CLOCK_SELECTION > 0) vlc_clock_setup (); while (clock_nanosleep (vlc_clock_id, 0, &ts, &ts) == EINTR); #else while (nanosleep (&ts, &ts) == -1) assert (errno == EINTR); #endif } unsigned vlc_GetCPUCount(void) { #if defined(HAVE_SCHED_GETAFFINITY) cpu_set_t cpu; CPU_ZERO(&cpu); if (sched_getaffinity (0, sizeof (cpu), &cpu) < 0) return 1; return CPU_COUNT (&cpu); #elif defined(__SunOS) unsigned count = 0; int type; u_int numcpus; processor_info_t cpuinfo; processorid_t *cpulist = vlc_alloc (sysconf(_SC_NPROCESSORS_MAX), sizeof (*cpulist)); if (unlikely(cpulist == NULL)) return 1; if (pset_info(PS_MYID, &type, &numcpus, cpulist) == 0) { for (u_int i = 0; i < numcpus; i++) if (processor_info (cpulist[i], &cpuinfo) == 0) count += (cpuinfo.pi_state == P_ONLINE); } else count = sysconf (_SC_NPROCESSORS_ONLN); free (cpulist); return count ? count : 1; #elif defined(_SC_NPROCESSORS_ONLN) return sysconf(_SC_NPROCESSORS_ONLN); #elif defined(_SC_NPROCESSORS_CONF) return sysconf(_SC_NPROCESSORS_CONF); #else # warning "vlc_GetCPUCount is not implemented for your platform" return 1; #endif }