/*****************************************************************************
* thread.c : pthread back-end for LibVLC
*****************************************************************************
* Copyright (C) 1999-2009 VLC authors and VideoLAN
*
* Authors: Jean-Marc Dressler <polux@via.ecp.fr>
* Samuel Hocevar <sam@zoy.org>
* Gildas Bazin <gbazin@netcourrier.com>
* 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 <vlc_common.h>
#include <vlc_atomic.h>
#include "libvlc.h"
#include <stdarg.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <sys/types.h>
#include <unistd.h> /* fsync() */
#include <pthread.h>
#include <sched.h>
#ifdef __linux__
# include <sys/syscall.h> /* SYS_gettid */
#endif
#ifdef HAVE_EXECINFO_H
# include <execinfo.h>
#endif
#if defined(__SunOS)
# include <sys/processor.h>
# include <sys/pset.h>
#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 <sys/time.h> /* gettimeofday() */
# if defined (HAVE_DECL_NANOSLEEP) && !HAVE_DECL_NANOSLEEP
int nanosleep (struct timespec *, struct timespec *);
# endif
# define vlc_clock_setup() (void)0
# warning Monotonic clock not available. Expect timing issues.
#endif /* _POSIX_TIMERS */
static struct timespec mtime_to_ts (mtime_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);
}
static inline unsigned long vlc_threadid (void)
{
#if defined (__linux__)
/* glibc does not provide a call for this */
return syscall (__NR_gettid);
#else
union { pthread_t th; unsigned long int i; } v = { };
v.th = pthread_self ();
return v.i;
#endif
}
#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_threadid ());
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
/**
* Initializes a fast mutex.
*/
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 );
}
/**
* Initializes a recursive mutex.
* \warning This is strongly discouraged. Please use normal mutexes.
*/
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 );
}
/**
* 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)
{
int val = pthread_mutex_destroy( p_mutex );
VLC_THREAD_ASSERT ("destroying mutex");
}
#ifndef NDEBUG
# ifdef HAVE_VALGRIND_VALGRIND_H
# include <valgrind/valgrind.h>
# 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
/**
* 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.
* This function is not a cancellation-point.
*
* @param p_mutex mutex initialized with vlc_mutex_init() or
* vlc_mutex_init_recursive()
*/
void vlc_mutex_lock (vlc_mutex_t *p_mutex)
{
int val = pthread_mutex_lock( p_mutex );
VLC_THREAD_ASSERT ("locking mutex");
}
/**
* Acquires a mutex if and only if it is not currently held by another thread.
* This function never sleeps and can be used in delay-critical code paths.
* This function is not a cancellation-point.
*
* <b>Beware</b>: If this function fails, then the mutex is held... by another
* thread. The calling thread must deal with the error appropriately. That
* typically implies postponing the operations that would have required the
* mutex. If the thread cannot defer those operations, then it must use
* vlc_mutex_lock(). If in doubt, use vlc_mutex_lock() instead.
*
* @param p_mutex mutex initialized with vlc_mutex_init() or
* vlc_mutex_init_recursive()
* @return 0 if the mutex could be acquired, an error code otherwise.
*/
int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
{
int val = pthread_mutex_trylock(