mirror of
https://github.com/ossrs/srs.git
synced 2025-02-15 04:42:04 +00:00
fix https://github.com/ossrs/srs/issues/3155 Build srt-1-fit fails with `standard attributes in middle of decl-specifiers` on GCC 12,Arch Linux. See https://github.com/Haivision/srt/releases/tag/v1.5.3
572 lines
16 KiB
C++
572 lines
16 KiB
C++
/*
|
|
* SRT - Secure, Reliable, Transport
|
|
* Copyright (c) 2019 Haivision Systems Inc.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
*/
|
|
#include "platform_sys.h"
|
|
|
|
#include <iomanip>
|
|
#include <math.h>
|
|
#include <stdexcept>
|
|
#include "sync.h"
|
|
#include "utilities.h"
|
|
#include "udt.h"
|
|
#include "srt.h"
|
|
#include "srt_compat.h"
|
|
#include "logging.h"
|
|
#include "common.h"
|
|
|
|
#if defined(_WIN32)
|
|
#include "win/wintime.h"
|
|
#include <sys/timeb.h>
|
|
#elif TARGET_OS_MAC
|
|
#include <mach/mach_time.h>
|
|
#endif
|
|
|
|
namespace srt_logging
|
|
{
|
|
extern Logger inlog;
|
|
}
|
|
using namespace srt_logging;
|
|
|
|
namespace srt
|
|
{
|
|
namespace sync
|
|
{
|
|
|
|
static void rdtsc(uint64_t& x)
|
|
{
|
|
#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_IA32_RDTSC
|
|
uint32_t lval, hval;
|
|
// asm volatile ("push %eax; push %ebx; push %ecx; push %edx");
|
|
// asm volatile ("xor %eax, %eax; cpuid");
|
|
asm volatile("rdtsc" : "=a"(lval), "=d"(hval));
|
|
// asm volatile ("pop %edx; pop %ecx; pop %ebx; pop %eax");
|
|
x = hval;
|
|
x = (x << 32) | lval;
|
|
#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_IA64_ITC
|
|
asm("mov %0=ar.itc" : "=r"(x)::"memory");
|
|
#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_AMD64_RDTSC
|
|
uint32_t lval, hval;
|
|
asm volatile("rdtsc" : "=a"(lval), "=d"(hval));
|
|
x = hval;
|
|
x = (x << 32) | lval;
|
|
#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_WINQPC
|
|
// This function should not fail, because we checked the QPC
|
|
// when calling to QueryPerformanceFrequency. If it failed,
|
|
// the m_bUseMicroSecond was set to true.
|
|
QueryPerformanceCounter((LARGE_INTEGER*)&x);
|
|
#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_MACH_ABSTIME
|
|
x = mach_absolute_time();
|
|
#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC
|
|
// get_cpu_frequency() returns 1 us accuracy in this case
|
|
timespec tm;
|
|
clock_gettime(CLOCK_MONOTONIC, &tm);
|
|
x = tm.tv_sec * uint64_t(1000000) + (tm.tv_nsec / 1000);
|
|
#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_POSIX_GETTIMEOFDAY
|
|
// use system call to read time clock for other archs
|
|
timeval t;
|
|
gettimeofday(&t, 0);
|
|
x = t.tv_sec * uint64_t(1000000) + t.tv_usec;
|
|
#else
|
|
#error Wrong SRT_SYNC_CLOCK
|
|
#endif
|
|
}
|
|
|
|
static int64_t get_cpu_frequency()
|
|
{
|
|
int64_t frequency = 1; // 1 tick per microsecond.
|
|
|
|
#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_WINQPC
|
|
LARGE_INTEGER ccf; // in counts per second
|
|
if (QueryPerformanceFrequency(&ccf))
|
|
{
|
|
frequency = ccf.QuadPart / 1000000; // counts per microsecond
|
|
if (frequency == 0)
|
|
{
|
|
LOGC(inlog.Warn, log << "Win QPC frequency of " << ccf.QuadPart
|
|
<< " counts/s is below the required 1 us accuracy. Please consider using C++11 timing (-DENABLE_STDCXX_SYNC=ON) instead.");
|
|
frequency = 1; // set back to 1 to avoid division by zero.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Can't throw an exception, it won't be handled.
|
|
LOGC(inlog.Error, log << "IPE: QueryPerformanceFrequency failed with " << GetLastError());
|
|
}
|
|
|
|
#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_MACH_ABSTIME
|
|
mach_timebase_info_data_t info;
|
|
mach_timebase_info(&info);
|
|
frequency = info.denom * int64_t(1000) / info.numer;
|
|
|
|
#elif SRT_SYNC_CLOCK >= SRT_SYNC_CLOCK_AMD64_RDTSC && SRT_SYNC_CLOCK <= SRT_SYNC_CLOCK_IA64_ITC
|
|
// SRT_SYNC_CLOCK_AMD64_RDTSC or SRT_SYNC_CLOCK_IA32_RDTSC or SRT_SYNC_CLOCK_IA64_ITC
|
|
uint64_t t1, t2;
|
|
|
|
rdtsc(t1);
|
|
timespec ts;
|
|
ts.tv_sec = 0;
|
|
ts.tv_nsec = 100000000;
|
|
nanosleep(&ts, NULL);
|
|
rdtsc(t2);
|
|
|
|
// CPU clocks per microsecond
|
|
frequency = int64_t(t2 - t1) / 100000;
|
|
#endif
|
|
|
|
return frequency;
|
|
}
|
|
|
|
static int count_subsecond_precision(int64_t ticks_per_us)
|
|
{
|
|
int signs = 6; // starting from 1 us
|
|
while (ticks_per_us /= 10) ++signs;
|
|
return signs;
|
|
}
|
|
|
|
const int64_t s_clock_ticks_per_us = get_cpu_frequency();
|
|
|
|
const int s_clock_subsecond_precision = count_subsecond_precision(s_clock_ticks_per_us);
|
|
|
|
int clockSubsecondPrecision() { return s_clock_subsecond_precision; }
|
|
|
|
} // namespace sync
|
|
} // namespace srt
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Sync utilities section
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static timespec us_to_timespec(const uint64_t time_us)
|
|
{
|
|
timespec timeout;
|
|
timeout.tv_sec = time_us / 1000000;
|
|
timeout.tv_nsec = (time_us % 1000000) * 1000;
|
|
return timeout;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// TimePoint section
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
template <>
|
|
srt::sync::Duration<srt::sync::steady_clock> srt::sync::TimePoint<srt::sync::steady_clock>::time_since_epoch() const
|
|
{
|
|
return srt::sync::Duration<srt::sync::steady_clock>(m_timestamp);
|
|
}
|
|
|
|
srt::sync::TimePoint<srt::sync::steady_clock> srt::sync::steady_clock::now()
|
|
{
|
|
uint64_t x = 0;
|
|
rdtsc(x);
|
|
return TimePoint<steady_clock>(x);
|
|
}
|
|
|
|
int64_t srt::sync::count_microseconds(const steady_clock::duration& t)
|
|
{
|
|
return t.count() / s_clock_ticks_per_us;
|
|
}
|
|
|
|
int64_t srt::sync::count_milliseconds(const steady_clock::duration& t)
|
|
{
|
|
return t.count() / s_clock_ticks_per_us / 1000;
|
|
}
|
|
|
|
int64_t srt::sync::count_seconds(const steady_clock::duration& t)
|
|
{
|
|
return t.count() / s_clock_ticks_per_us / 1000000;
|
|
}
|
|
|
|
srt::sync::steady_clock::duration srt::sync::microseconds_from(int64_t t_us)
|
|
{
|
|
return steady_clock::duration(t_us * s_clock_ticks_per_us);
|
|
}
|
|
|
|
srt::sync::steady_clock::duration srt::sync::milliseconds_from(int64_t t_ms)
|
|
{
|
|
return steady_clock::duration((1000 * t_ms) * s_clock_ticks_per_us);
|
|
}
|
|
|
|
srt::sync::steady_clock::duration srt::sync::seconds_from(int64_t t_s)
|
|
{
|
|
return steady_clock::duration((1000000 * t_s) * s_clock_ticks_per_us);
|
|
}
|
|
|
|
srt::sync::Mutex::Mutex()
|
|
{
|
|
const int err = pthread_mutex_init(&m_mutex, 0);
|
|
if (err)
|
|
{
|
|
throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
}
|
|
|
|
srt::sync::Mutex::~Mutex()
|
|
{
|
|
pthread_mutex_destroy(&m_mutex);
|
|
}
|
|
|
|
int srt::sync::Mutex::lock()
|
|
{
|
|
return pthread_mutex_lock(&m_mutex);
|
|
}
|
|
|
|
int srt::sync::Mutex::unlock()
|
|
{
|
|
return pthread_mutex_unlock(&m_mutex);
|
|
}
|
|
|
|
bool srt::sync::Mutex::try_lock()
|
|
{
|
|
return (pthread_mutex_trylock(&m_mutex) == 0);
|
|
}
|
|
|
|
srt::sync::ScopedLock::ScopedLock(Mutex& m)
|
|
: m_mutex(m)
|
|
{
|
|
m_mutex.lock();
|
|
}
|
|
|
|
srt::sync::ScopedLock::~ScopedLock()
|
|
{
|
|
m_mutex.unlock();
|
|
}
|
|
|
|
|
|
srt::sync::UniqueLock::UniqueLock(Mutex& m)
|
|
: m_Mutex(m)
|
|
{
|
|
m_iLocked = m_Mutex.lock();
|
|
}
|
|
|
|
srt::sync::UniqueLock::~UniqueLock()
|
|
{
|
|
if (m_iLocked == 0)
|
|
{
|
|
unlock();
|
|
}
|
|
}
|
|
|
|
void srt::sync::UniqueLock::lock()
|
|
{
|
|
if (m_iLocked != -1)
|
|
throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0);
|
|
|
|
m_iLocked = m_Mutex.lock();
|
|
}
|
|
|
|
void srt::sync::UniqueLock::unlock()
|
|
{
|
|
if (m_iLocked != 0)
|
|
throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0);
|
|
|
|
m_Mutex.unlock();
|
|
m_iLocked = -1;
|
|
}
|
|
|
|
srt::sync::Mutex* srt::sync::UniqueLock::mutex()
|
|
{
|
|
return &m_Mutex;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Condition section (based on pthreads)
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace srt
|
|
{
|
|
namespace sync
|
|
{
|
|
|
|
Condition::Condition()
|
|
#ifdef _WIN32
|
|
: m_cv(PTHREAD_COND_INITIALIZER)
|
|
#endif
|
|
{}
|
|
|
|
Condition::~Condition() {}
|
|
|
|
void Condition::init()
|
|
{
|
|
pthread_condattr_t* attr = NULL;
|
|
#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC
|
|
pthread_condattr_t CondAttribs;
|
|
pthread_condattr_init(&CondAttribs);
|
|
pthread_condattr_setclock(&CondAttribs, CLOCK_MONOTONIC);
|
|
attr = &CondAttribs;
|
|
#endif
|
|
const int res = pthread_cond_init(&m_cv, attr);
|
|
if (res != 0)
|
|
throw std::runtime_error("pthread_cond_init monotonic failed");
|
|
}
|
|
|
|
void Condition::destroy()
|
|
{
|
|
pthread_cond_destroy(&m_cv);
|
|
}
|
|
|
|
void Condition::wait(UniqueLock& lock)
|
|
{
|
|
pthread_cond_wait(&m_cv, &lock.mutex()->ref());
|
|
}
|
|
|
|
bool Condition::wait_for(UniqueLock& lock, const steady_clock::duration& rel_time)
|
|
{
|
|
timespec timeout;
|
|
#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC
|
|
clock_gettime(CLOCK_MONOTONIC, &timeout);
|
|
const uint64_t now_us = timeout.tv_sec * uint64_t(1000000) + (timeout.tv_nsec / 1000);
|
|
#else
|
|
timeval now;
|
|
gettimeofday(&now, 0);
|
|
const uint64_t now_us = now.tv_sec * uint64_t(1000000) + now.tv_usec;
|
|
#endif
|
|
timeout = us_to_timespec(now_us + count_microseconds(rel_time));
|
|
return pthread_cond_timedwait(&m_cv, &lock.mutex()->ref(), &timeout) != ETIMEDOUT;
|
|
}
|
|
|
|
bool Condition::wait_until(UniqueLock& lock, const steady_clock::time_point& timeout_time)
|
|
{
|
|
// This will work regardless as to which clock is in use. The time
|
|
// should be specified as steady_clock::time_point, so there's no
|
|
// question of the timer base.
|
|
const steady_clock::time_point now = steady_clock::now();
|
|
if (now >= timeout_time)
|
|
return false; // timeout
|
|
|
|
// wait_for() is used because it will be converted to pthread-frienly timeout_time inside.
|
|
return wait_for(lock, timeout_time - now);
|
|
}
|
|
|
|
void Condition::notify_one()
|
|
{
|
|
pthread_cond_signal(&m_cv);
|
|
}
|
|
|
|
void Condition::notify_all()
|
|
{
|
|
pthread_cond_broadcast(&m_cv);
|
|
}
|
|
|
|
}; // namespace sync
|
|
}; // namespace srt
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CThread class
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
srt::sync::CThread::CThread()
|
|
{
|
|
m_thread = pthread_t();
|
|
}
|
|
|
|
srt::sync::CThread::CThread(void *(*start_routine) (void *), void *arg)
|
|
{
|
|
create(start_routine, arg);
|
|
}
|
|
|
|
#if HAVE_FULL_CXX11
|
|
srt::sync::CThread& srt::sync::CThread::operator=(CThread&& other)
|
|
#else
|
|
srt::sync::CThread& srt::sync::CThread::operator=(CThread& other)
|
|
#endif
|
|
{
|
|
if (joinable())
|
|
{
|
|
// If the thread has already terminated, then
|
|
// pthread_join() returns immediately.
|
|
// But we have to check it has terminated before replacing it.
|
|
LOGC(inlog.Error, log << "IPE: Assigning to a thread that is not terminated!");
|
|
|
|
#ifndef DEBUG
|
|
#ifndef __ANDROID__
|
|
// In case of production build the hanging thread should be terminated
|
|
// to avoid hang ups and align with C++11 implementation.
|
|
// There is no pthread_cancel on Android. See #1476. This error should not normally
|
|
// happen, but if it happen, then detaching the thread.
|
|
pthread_cancel(m_thread);
|
|
#endif // __ANDROID__
|
|
#else
|
|
join();
|
|
#endif
|
|
}
|
|
|
|
// Move thread handler from other
|
|
m_thread = other.m_thread;
|
|
other.m_thread = pthread_t();
|
|
return *this;
|
|
}
|
|
|
|
#if !HAVE_FULL_CXX11
|
|
void srt::sync::CThread::create_thread(void *(*start_routine) (void *), void *arg)
|
|
{
|
|
SRT_ASSERT(!joinable());
|
|
create(start_routine, arg);
|
|
}
|
|
#endif
|
|
|
|
bool srt::sync::CThread::joinable() const
|
|
{
|
|
return !pthread_equal(m_thread, pthread_t());
|
|
}
|
|
|
|
void srt::sync::CThread::join()
|
|
{
|
|
void *retval;
|
|
const int ret SRT_ATR_UNUSED = pthread_join(m_thread, &retval);
|
|
if (ret != 0)
|
|
{
|
|
LOGC(inlog.Error, log << "pthread_join failed with " << ret);
|
|
}
|
|
#ifdef HEAVY_LOGGING
|
|
else
|
|
{
|
|
HLOGC(inlog.Debug, log << "pthread_join SUCCEEDED");
|
|
}
|
|
#endif
|
|
// After joining, joinable should be false
|
|
m_thread = pthread_t();
|
|
return;
|
|
}
|
|
|
|
void srt::sync::CThread::create(void *(*start_routine) (void *), void *arg)
|
|
{
|
|
const int st = pthread_create(&m_thread, NULL, start_routine, arg);
|
|
if (st != 0)
|
|
{
|
|
LOGC(inlog.Error, log << "pthread_create failed with " << st);
|
|
throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0);
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CThreadError class - thread local storage error wrapper
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
namespace srt {
|
|
namespace sync {
|
|
|
|
class CThreadError
|
|
{
|
|
public:
|
|
CThreadError()
|
|
{
|
|
pthread_key_create(&m_ThreadSpecKey, ThreadSpecKeyDestroy);
|
|
|
|
// This is a global object and as such it should be called in the
|
|
// main application thread or at worst in the thread that has first
|
|
// run `srt_startup()` function and so requested the SRT library to
|
|
// be dynamically linked. Most probably in this very thread the API
|
|
// errors will be reported, so preallocate the ThreadLocalSpecific
|
|
// object for this error description.
|
|
|
|
// This allows std::bac_alloc to crash the program during
|
|
// the initialization of the SRT library (likely it would be
|
|
// during the DL constructor, still way before any chance of
|
|
// doing any operations here). This will prevent SRT from running
|
|
// into trouble while trying to operate.
|
|
CUDTException* ne = new CUDTException();
|
|
pthread_setspecific(m_ThreadSpecKey, ne);
|
|
}
|
|
|
|
~CThreadError()
|
|
{
|
|
// Likely all objects should be deleted in all
|
|
// threads that have exited, but std::this_thread didn't exit
|
|
// yet :).
|
|
ThreadSpecKeyDestroy(pthread_getspecific(m_ThreadSpecKey));
|
|
pthread_key_delete(m_ThreadSpecKey);
|
|
}
|
|
|
|
void set(const CUDTException& e)
|
|
{
|
|
CUDTException* cur = get();
|
|
// If this returns NULL, it means that there was an unexpected
|
|
// memory allocation error. Simply ignore this request if so
|
|
// happened, and then when trying to get the error description
|
|
// the application will always get the memory allocation error.
|
|
|
|
// There's no point in doing anything else here; lack of memory
|
|
// must be prepared for prematurely, and that was already done.
|
|
if (!cur)
|
|
return;
|
|
|
|
*cur = e;
|
|
}
|
|
|
|
/*[[nullable]]*/ CUDTException* get()
|
|
{
|
|
if (!pthread_getspecific(m_ThreadSpecKey))
|
|
{
|
|
// This time if this can't be done due to memory allocation
|
|
// problems, just allow this value to be NULL, which during
|
|
// getting the error description will redirect to a memory
|
|
// allocation error.
|
|
|
|
// It would be nice to somehow ensure that this object is
|
|
// created in every thread of the application using SRT, but
|
|
// POSIX thread API doesn't contain any possibility to have
|
|
// a creation callback that would apply to every thread in
|
|
// the application (as it is for C++11 thread_local storage).
|
|
CUDTException* ne = new(std::nothrow) CUDTException();
|
|
pthread_setspecific(m_ThreadSpecKey, ne);
|
|
return ne;
|
|
}
|
|
return (CUDTException*)pthread_getspecific(m_ThreadSpecKey);
|
|
}
|
|
|
|
static void ThreadSpecKeyDestroy(void* e)
|
|
{
|
|
delete (CUDTException*)e;
|
|
}
|
|
|
|
private:
|
|
pthread_key_t m_ThreadSpecKey;
|
|
};
|
|
|
|
// Threal local error will be used by CUDTUnited
|
|
// that has a static scope
|
|
|
|
// This static makes this object file-private access so that
|
|
// the access is granted only for the accessor functions.
|
|
static CThreadError s_thErr;
|
|
|
|
void SetThreadLocalError(const CUDTException& e)
|
|
{
|
|
s_thErr.set(e);
|
|
}
|
|
|
|
CUDTException& GetThreadLocalError()
|
|
{
|
|
// In POSIX version we take into account the possibility
|
|
// of having an allocation error here. Therefore we need to
|
|
// allow thie value to return NULL and have some fallback
|
|
// for that case. The dynamic memory allocation failure should
|
|
// be the only case as to why it is unable to get the pointer
|
|
// to the error description.
|
|
static CUDTException resident_alloc_error (MJ_SYSTEMRES, MN_MEMORY);
|
|
CUDTException* curx = s_thErr.get();
|
|
if (!curx)
|
|
return resident_alloc_error;
|
|
return *curx;
|
|
}
|
|
|
|
} // namespace sync
|
|
} // namespace srt
|
|
|