mirror of
				https://github.com/ossrs/srs.git
				synced 2025-03-09 15:49:59 +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
 | |
| 
 |