From 5c28eac0eb5fc2caa1b6313b09e9b45b8cf711df Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Mon, 11 May 2026 18:27:52 -0700 Subject: [PATCH] Break out a few misc things from steamnetworkingsockets_socketthread.cpp Lock debugging Timer Memory override --- src/CMakeLists.txt | 1 + .../steamnetworkingsockets_lowlevel.h | 59 +- .../steamnetworkingsockets_lowlevel_misc.cpp | 604 ++++++++++++++++++ .../steamnetworkingsockets_socketthread.cpp | 563 +--------------- 4 files changed, 643 insertions(+), 584 deletions(-) create mode 100644 src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel_misc.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 14ec093..65ce051 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,7 @@ set(GNS_CLIENTLIB_SRCS "steamnetworkingsockets/clientlib/steamnetworkingsockets_flat.cpp" "steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp" "steamnetworkingsockets/clientlib/steamnetworkingsockets_socketthread.cpp" + "steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel_misc.cpp" "steamnetworkingsockets/clientlib/steamnetworkingsockets_spew.cpp" "steamnetworkingsockets/clientlib/steamnetworkingsockets_taskqueue.cpp" "steamnetworkingsockets/clientlib/steamnetworkingsockets_ice_client.cpp" diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.h b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.h index a78b9c9..19cb6a4 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.h +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.h @@ -321,16 +321,29 @@ private: extern int g_cbUDPSocketBufferSize; -///////////////////////////////////////////////////////////////////////////// -// -// Misc low level service thread stuff -// -///////////////////////////////////////////////////////////////////////////// - /// Called when we know it's safe to actually destroy sockets pending deletion. /// This is when: 1.) We own the lock and 2.) we aren't polling in the service thread. extern void ProcessPendingDestroyClosedRawUDPSockets(); +/// Return true if the service thread is running +extern bool IsServiceThreadRunning(); + +/// Wake up the service thread ASAP. Intended to be called from other threads, +/// but is safe to call from the service thread as well. +extern void WakeServiceThread(); + +/// Return true if it looks like the address is a local address +extern bool IsRouteToAddressProbablyLocal( netadr_t addr ); + +extern bool ResolveHostname( const char* pszHostname, CUtlVector< SteamNetworkingIPAddr > *pAddrs ); +extern bool GetLocalAddresses( CUtlVector< SteamNetworkingIPAddr >* pAddrs ); + +///////////////////////////////////////////////////////////////////////////// +// +// Spew +// +///////////////////////////////////////////////////////////////////////////// + /// Last time that we spewed something that was subject to rate limit extern SteamNetworkingMicroseconds g_usecLastRateLimitSpew; extern int g_nRateLimitSpewCount; @@ -628,10 +641,12 @@ using ShortDurationScopeLock = ScopeLock; #define AssertHeldByCurrentThread( ... ) _AssertHeldByCurrentThread( __FILE__, __LINE__ ,## __VA_ARGS__ ) #define AssertLocksHeldByCurrentThread( ... ) _AssertLocksHeldByCurrentThread( __FILE__, __LINE__,## __VA_ARGS__ ) #define TakeLockOwnership( pLock, ... ) _TakeLockOwnership( (pLock), __FILE__, __LINE__,## __VA_ARGS__ ) + extern void AssertGlobalLockHeldExactlyOnce(); #else #define AssertHeldByCurrentThread( ... ) _AssertHeldByCurrentThread( nullptr, 0,## __VA_ARGS__ ) #define AssertLocksHeldByCurrentThread( ... ) _AssertLocksHeldByCurrentThread( nullptr, 0,## __VA_ARGS__ ) #define TakeLockOwnership( pLock, ... ) _TakeLockOwnership( (pLock), nullptr, 0,## __VA_ARGS__ ) + inline void AssertGlobalLockHeldExactlyOnce() {} #endif /// Special utilities for acquiring the global lock @@ -654,16 +669,13 @@ struct SteamNetworkingGlobalLock #endif }; -#ifdef DBGFLAG_VALIDATE -extern void SteamNetworkingSocketsLowLevelValidate( CValidator &validator ); -#endif +extern SteamNetworkingMicroseconds s_usecServiceThreadLockWaitWarning; -/// Return true if the service thread is running -extern bool IsServiceThreadRunning(); - -/// Wake up the service thread ASAP. Intended to be called from other threads, -/// but is safe to call from the service thread as well. -extern void WakeServiceThread(); +///////////////////////////////////////////////////////////////////////////// +// +// Task queue +// +///////////////////////////////////////////////////////////////////////////// class CQueuedTask; @@ -826,20 +838,21 @@ extern CTaskList g_taskListRunInBackground; // ///////////////////////////////////////////////////////////////////////////// -/// Fetch current time -extern SteamNetworkingMicroseconds SteamNetworkingSockets_GetLocalTimestamp(); +#ifdef DBGFLAG_VALIDATE +extern void SteamNetworkingSocketsLowLevelValidate( CValidator &validator ); +#endif + +extern void SeedWeakRandomGenerator(); +extern void ETW_LongOp( const char *opName, SteamNetworkingMicroseconds usec, const char *pszInfo ); /// Set debug output hook extern void SteamNetworkingSockets_SetDebugOutputFunction( ESteamNetworkingSocketsDebugOutputType eDetailLevel, FSteamNetworkingSocketsDebugOutput pfnFunc ); -/// Return true if it looks like the address is a local address -extern bool IsRouteToAddressProbablyLocal( netadr_t addr ); - -extern bool ResolveHostname( const char* pszHostname, CUtlVector< SteamNetworkingIPAddr > *pAddrs ); -extern bool GetLocalAddresses( CUtlVector< SteamNetworkingIPAddr >* pAddrs ); - } // namespace SteamNetworkingSocketsLib +/// Fetch current time +extern SteamNetworkingMicroseconds SteamNetworkingSockets_GetLocalTimestamp(); + STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_DefaultPreFormatDebugOutputHandler( ESteamNetworkingSocketsDebugOutputType eType, bool bFmt, const char* pstrFile, int nLine, const char *pMsg, va_list ap ); #endif // STEAMNETWORKINGSOCKETS_LOWLEVEL_H diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel_misc.cpp b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel_misc.cpp new file mode 100644 index 0000000..737de29 --- /dev/null +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel_misc.cpp @@ -0,0 +1,604 @@ +//====== Copyright Valve Corporation, All rights reserved. ==================== +// +// Miscellaneous "low level" functionality for SteamNetworkingSockets. +// +// - Global lock (mutex) and lock debugging +// - Local timestamp clock +// - Memory allocator override +// +#include "steamnetworkingsockets_lowlevel.h" +#include "crypto.h" +#include +#include + +#if IsPosix() + #include +#endif + +#include + +TRACELOGGING_DECLARE_PROVIDER( HTraceLogging_SteamNetworkingSockets ); + +using namespace SteamNetworkingSocketsLib; +namespace SteamNetworkingSocketsLib { + +void ETW_LongOp( const char *opName, SteamNetworkingMicroseconds usec, const char *pszInfo ) +{ + if ( !pszInfo ) + pszInfo = ""; + TraceLoggingWrite( + HTraceLogging_SteamNetworkingSockets, + "LongOp", + TraceLoggingLevel( WINEVENT_LEVEL_WARNING ), + TraceLoggingUInt64( usec, "Microseconds" ), + TraceLoggingString( pszInfo, "ExtraInfo" ) + ); +} + +void SeedWeakRandomGenerator() +{ + + // Seed cheesy random number generator using true source of entropy + int temp; + CCrypto::GenerateRandomBlock( &temp, sizeof(temp) ); + WeakRandomSeed( temp ); +} + +///////////////////////////////////////////////////////////////////////////// +// +// Lock debugging / hygiene +// +///////////////////////////////////////////////////////////////////////////// + +/// Global lock for all local data structures +static Lock s_mutexGlobalLock( "global", 0, LockDebugInfo::k_nOrder_Global ); + +// Threshold for the service-thread-specific *assert* +#ifdef __SANITIZE_THREAD__ +SteamNetworkingMicroseconds s_usecServiceThreadLockWaitWarning = 300*1000; +#else +SteamNetworkingMicroseconds s_usecServiceThreadLockWaitWarning = 50*1000; +#endif + + +#if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 + +// By default, complain if we hold the lock for more than this long. +// TSan adds 10-20x overhead so the threshold is scaled up accordingly. +#ifdef __SANITIZE_THREAD__ +constexpr SteamNetworkingMicroseconds k_usecDefaultLongLockHeldWarningThreshold = 5*1000*20; +#else +constexpr SteamNetworkingMicroseconds k_usecDefaultLongLockHeldWarningThreshold = 5*1000; +#endif + +// Threshold for warning about waiting on any lock. +#ifdef __SANITIZE_THREAD__ +static SteamNetworkingMicroseconds s_usecLockWaitWarningThreshold = 300*1000; +#else +static SteamNetworkingMicroseconds s_usecLockWaitWarningThreshold = 2*1000; +#endif + +// Debug the locks active on the cu +struct ThreadLockDebugInfo +{ + static constexpr int k_nMaxHeldLocks = 8; + static constexpr int k_nMaxTags = 32; + + int m_nHeldLocks = 0; + int m_nTags = 0; + + SteamNetworkingMicroseconds m_usecLongLockWarningThreshold; + SteamNetworkingMicroseconds m_usecIgnoreLongLockWaitTimeUntil; + SteamNetworkingMicroseconds m_usecOuterLockStartTime; // Time when we started waiting on outermost lock (if we don't have it yet), or when we aquired the lock (if we have it) + + const LockDebugInfo *m_arHeldLocks[ k_nMaxHeldLocks ]; + struct Tag_t + { + const char *m_pszTag; + int m_nCount; + }; + Tag_t m_arTags[ k_nMaxTags ]; + + inline void AddTag( const char *pszTag ); +}; + +static void (*s_fLockAcquiredCallback)( const char *tags, SteamNetworkingMicroseconds usecWaited ); +static void (*s_fLockHeldCallback)( const char *tags, SteamNetworkingMicroseconds usecWaited ); + +/// Get the per-thread debug info +static ThreadLockDebugInfo &GetThreadDebugInfo() +{ + // Apple seems to hate thread_local. Is there some sort of feature + // define a can check here? It's a shame because it's really very + // efficient on MSVC, gcc, and clang on Windows and linux. + // + // Apple seems to support thread_local starting with Xcode 8.0 + #if defined(__APPLE__) && __clang_major__ < 8 + + static pthread_key_t key; + static pthread_once_t key_once = PTHREAD_ONCE_INIT; + + // One time init the TLS key + pthread_once( &key_once, + [](){ // Initialization code to run once + pthread_key_create( + &key, + [](void *ptr) { free(ptr); } // Destructor function + ); + } + ); + + // Get current object + void *result = pthread_getspecific(key); + if ( unlikely( result == nullptr ) ) + { + result = malloc( sizeof(ThreadLockDebugInfo) ); + memset( result, 0, sizeof(ThreadLockDebugInfo) ); + pthread_setspecific(key, result); + } + return *static_cast( result ); + #else + + // Use thread_local + thread_local ThreadLockDebugInfo tls_lockDebugInfo; + return tls_lockDebugInfo; + #endif +} + +/// If non-NULL, add a "tag" to the lock journal for the current thread. +/// This is useful so that if we hold a lock for a long time, we can get +/// an idea what sorts of operations were taking a long time. +inline void ThreadLockDebugInfo::AddTag( const char *pszTag ) +{ + if ( !pszTag ) + return; + + Assert( m_nHeldLocks > 0 ); // Can't add a tag unless we are locked! + + for ( int i = 0 ; i < m_nTags ; ++i ) + { + if ( m_arTags[i].m_pszTag == pszTag ) + { + ++m_arTags[i].m_nCount; + return; + } + } + + if ( m_nTags >= ThreadLockDebugInfo::k_nMaxTags ) + return; + + m_arTags[ m_nTags ].m_pszTag = pszTag; + m_arTags[ m_nTags ].m_nCount = 1; + ++m_nTags; +} + +LockDebugInfo::~LockDebugInfo() +{ + // We should not be locked! If we are, remove us + ThreadLockDebugInfo &t = GetThreadDebugInfo(); + for ( int i = t.m_nHeldLocks-1 ; i >= 0 ; --i ) + { + if ( t.m_arHeldLocks[i] == this ) + { + AssertMsg( false, "Lock '%s' being destroyed while it is held!", m_pszName ); + AboutToUnlock(); + } + } +} + +void LockDebugInfo::AboutToLock( bool bTry ) +{ + ThreadLockDebugInfo &t = GetThreadDebugInfo(); + + // First lock held by this thread? + if ( t.m_nHeldLocks == 0 ) + { + // Remember when we started trying to lock + t.m_usecOuterLockStartTime = SteamNetworkingSockets_GetLocalTimestamp(); + return; + } + + // We already hold a lock. Check for taking locks in such a way + // that might lead to deadlocks. + const LockDebugInfo *pTopLock = t.m_arHeldLocks[ t.m_nHeldLocks-1 ]; + + // Taking locks in increasing order is always allowed + if ( likely( pTopLock->m_nOrder < m_nOrder ) ) + return; + + // Global lock *must* always be the outermost lock. (It is legal to take other locks in + // between and then lock the global lock recursively.) + const bool bHoldGlobalLock = t.m_arHeldLocks[ 0 ] == &s_mutexGlobalLock; + AssertMsg( + bHoldGlobalLock || this != &s_mutexGlobalLock, + "Taking global lock while already holding lock '%s'", t.m_arHeldLocks[ 0 ]->m_pszName + ); + + // If they are only "trying", we allow out-of-order behaviour. + if ( bTry ) + return; + + // It's always OK to lock recursively. + // + // (Except for "short duration" locks, which are allowed to + // use a mutex implementation that does not support this.) + if ( !( m_nFlags & k_nFlag_ShortDuration ) ) + { + for ( int i = 0 ; i < t.m_nHeldLocks ; ++i ) + { + if ( t.m_arHeldLocks[i] == this ) + return; + } + } + + // Taking multiple object locks? This is allowed under certain circumstances + if ( likely( pTopLock->m_nOrder == m_nOrder && m_nOrder == k_nOrder_ObjectOrTable ) ) + { + + // If we hold the global lock, it's OK + if ( bHoldGlobalLock ) + return; + + // If the global lock isn't held, then no more than one + // object lock is allowed, since two different threads + // might take them in different order. + constexpr int k_nObjectFlags = LockDebugInfo::k_nFlag_Connection | LockDebugInfo::k_nFlag_PollGroup; + if ( + ( ( m_nFlags & k_nObjectFlags ) != 0 ) + //|| ( m_nFlags & k_nFlag_Table ) // We actually do this in one place when we know it's OK. Not worth it right now to get this situation exempted from the checking. + ) { + // We must not already hold any existing object locks (except perhaps this one) + for ( int i = 0 ; i < t.m_nHeldLocks ; ++i ) + { + const LockDebugInfo *pOtherLock = t.m_arHeldLocks[ i ]; + AssertMsg( pOtherLock == this || !( pOtherLock->m_nFlags & k_nObjectFlags ), + "Taking lock '%s' and then '%s', while not holding the global lock", pOtherLock->m_pszName, m_pszName ); + } + } + + // Usage is OK if we didn't find any problems above + return; + } + + AssertMsg( false, "Taking lock '%s' while already holding lock '%s'", m_pszName, pTopLock->m_pszName ); +} + +void LockDebugInfo::OnLocked( const char *pszTag ) +{ + ThreadLockDebugInfo &t = GetThreadDebugInfo(); + + Assert( t.m_nHeldLocks < ThreadLockDebugInfo::k_nMaxHeldLocks ); + t.m_arHeldLocks[ t.m_nHeldLocks++ ] = this; + + if ( t.m_nHeldLocks == 1 ) + { + SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp(); + SteamNetworkingMicroseconds usecTimeSpentWaitingOnLock = usecNow - t.m_usecOuterLockStartTime; + t.m_usecLongLockWarningThreshold = k_usecDefaultLongLockHeldWarningThreshold; + t.m_nTags = 0; + + if ( usecTimeSpentWaitingOnLock > s_usecLockWaitWarningThreshold && usecNow > t.m_usecIgnoreLongLockWaitTimeUntil ) + { + if ( pszTag ) + SpewWarning( "Waited %.1fms for SteamNetworkingSockets lock [%s]", usecTimeSpentWaitingOnLock*1e-3, pszTag ); + else + SpewWarning( "Waited %.1fms for SteamNetworkingSockets lock", usecTimeSpentWaitingOnLock*1e-3 ); + ETW_LongOp( "lock wait", usecTimeSpentWaitingOnLock, pszTag ); + } + + auto callback = s_fLockAcquiredCallback; // save to temp, to prevent very narrow race condition where variable is cleared after we null check it, and we call null + if ( callback ) + callback( pszTag, usecTimeSpentWaitingOnLock ); + + t.m_usecOuterLockStartTime = usecNow; + } + + t.AddTag( pszTag ); +} + +void LockDebugInfo::AboutToUnlock() +{ + char tags[ 256 ]; + + SteamNetworkingMicroseconds usecElapsed = 0; + SteamNetworkingMicroseconds usecElapsedTooLong = 0; + auto lockHeldCallback = s_fLockHeldCallback; + + ThreadLockDebugInfo &t = GetThreadDebugInfo(); + Assert( t.m_nHeldLocks > 0 ); + + // Unlocking the last lock? + if ( t.m_nHeldLocks == 1 ) + { + + // We're about to do the final release. How long did we hold the lock? + usecElapsed = SteamNetworkingSockets_GetLocalTimestamp() - t.m_usecOuterLockStartTime; + + // Too long? We need to check the threshold here because the threshold could + // change by another thread immediately after we release the lock. Also, if + // we're debugging, all bets are off. They could have hit a breakpoint, and + // we don't want to create a bunch of confusing spew with spurious asserts + if ( usecElapsed >= t.m_usecLongLockWarningThreshold && !Plat_IsInDebugSession() ) + { + usecElapsedTooLong = usecElapsed; + } + + if ( usecElapsedTooLong > 0 || lockHeldCallback ) + { + char *p = tags; + char *end = tags + sizeof(tags) - 1; + for ( int i = 0 ; i < t.m_nTags && p+5 < end ; ++i ) + { + if ( p > tags ) + *(p++) = ','; + + const ThreadLockDebugInfo::Tag_t &tag = t.m_arTags[i]; + int taglen = std::min( int(end-p), (int)V_strlen( tag.m_pszTag ) ); + memcpy( p, tag.m_pszTag, taglen ); + p += taglen; + + if ( tag.m_nCount > 1 ) + { + int l = end-p; + if ( l <= 5 ) + break; + p += V_snprintf( p, l, "(x%d)", tag.m_nCount ); + } + } + *p = '\0'; + } + + t.m_nTags = 0; + t.m_usecOuterLockStartTime = 0; // Just for grins. + } + + if ( usecElapsed > 0 && lockHeldCallback ) + { + lockHeldCallback(tags, usecElapsed); + } + + // Yelp if we held the lock for longer than the threshold. + if ( usecElapsedTooLong != 0 ) + { + SpewWarning( + "SteamNetworkingSockets lock held for %.1fms. (Performance warning.) %s\n" + "This is usually a symptom of a general performance problem such as thread starvation.", + usecElapsedTooLong*1e-3, tags + ); + ETW_LongOp( "lock held", usecElapsedTooLong, tags ); + } + + // NOTE: We are allowed to unlock out of order! We specifically + // do this with the table lock! + for ( int i = t.m_nHeldLocks-1 ; i >= 0 ; --i ) + { + if ( t.m_arHeldLocks[i] == this ) + { + --t.m_nHeldLocks; + if ( i < t.m_nHeldLocks ) // Don't do the memmove in the common case of stack pop + memmove( &t.m_arHeldLocks[i], &t.m_arHeldLocks[i+1], (t.m_nHeldLocks-i) * sizeof(t.m_arHeldLocks[0]) ); + t.m_arHeldLocks[t.m_nHeldLocks] = nullptr; // Just for grins + return; + } + } + + AssertMsg( false, "Unlocked a lock '%s' that wasn't held?", m_pszName ); +} + +void LockDebugInfo::_AssertHeldByCurrentThread( const char *pszFile, int line, const char *pszTag ) const +{ + ThreadLockDebugInfo &t = GetThreadDebugInfo(); + for ( int i = t.m_nHeldLocks-1 ; i >= 0 ; --i ) + { + if ( t.m_arHeldLocks[i] == this ) + { + t.AddTag( pszTag ); + return; + } + } + + AssertMsg( false, "%s(%d): Lock '%s' not held", pszFile, line, m_pszName ); +} + +void SteamNetworkingGlobalLock::SetLongLockWarningThresholdMS( const char *pszTag, int msWarningThreshold ) +{ + AssertHeldByCurrentThread( pszTag ); + SteamNetworkingMicroseconds usecWarningThreshold = SteamNetworkingMicroseconds{msWarningThreshold}*1000; + ThreadLockDebugInfo &t = GetThreadDebugInfo(); + if ( t.m_usecLongLockWarningThreshold < usecWarningThreshold ) + { + t.m_usecLongLockWarningThreshold = usecWarningThreshold; + t.m_usecIgnoreLongLockWaitTimeUntil = SteamNetworkingSockets_GetLocalTimestamp() + t.m_usecLongLockWarningThreshold; + } +} + +void SteamNetworkingGlobalLock::_AssertHeldByCurrentThread( const char *pszFile, int line ) +{ + s_mutexGlobalLock._AssertHeldByCurrentThread( pszFile, line, nullptr ); +} + +void SteamNetworkingGlobalLock::_AssertHeldByCurrentThread( const char *pszFile, int line, const char *pszTag ) +{ + s_mutexGlobalLock._AssertHeldByCurrentThread( pszFile, line, pszTag ); +} + +void AssertGlobalLockHeldExactlyOnce() +{ + ThreadLockDebugInfo &t = GetThreadDebugInfo(); + Assert( t.m_nHeldLocks == 1 && t.m_arHeldLocks[0] == &s_mutexGlobalLock ); +} + +#endif // #if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 + +void SteamNetworkingGlobalLock::Lock( const char *pszTag ) +{ + s_mutexGlobalLock.lock( pszTag ); +} + +bool SteamNetworkingGlobalLock::TryLock( const char *pszTag, int msTimeout ) +{ + return s_mutexGlobalLock.try_lock_for( msTimeout, pszTag ); +} + +void SteamNetworkingGlobalLock::Unlock() +{ + s_mutexGlobalLock.unlock(); +} + +} // namespace SteamNetworkingSocketsLib + +STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_SetLockWaitWarningThreshold( SteamNetworkingMicroseconds usecTheshold ) +{ + #if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 + s_usecLockWaitWarningThreshold = usecTheshold; + #endif + s_usecServiceThreadLockWaitWarning = usecTheshold; +} + +STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_SetLockAcquiredCallback( void (*callback)( const char *tags, SteamNetworkingMicroseconds usecWaited ) ) +{ + #if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 + s_fLockAcquiredCallback = callback; + #else + // Should we assert here? + #endif +} + +STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_SetLockHeldCallback( void (*callback)( const char *tags, SteamNetworkingMicroseconds usecWaited ) ) +{ + #if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 + s_fLockHeldCallback = callback; + #else + // Should we assert here? + #endif +} + +///////////////////////////////////////////////////////////////////////////// +// +// Local timestamp +// +///////////////////////////////////////////////////////////////////////////// + +namespace SteamNetworkingSocketsLib { + +static std::atomic s_usecTimeLastReturned; + +// Start with an offset so that a timestamp of zero is always pretty far in the past. +// But round it up to nice round number, so that looking at timestamps in the debugger +// is easy to read. +const long long k_nInitialTimestampMin = k_nMillion*24*3600*30; +const long long k_nInitialTimestamp = 3000000000000ll; +COMPILE_TIME_ASSERT( 2000000000000ll < k_nInitialTimestampMin ); +COMPILE_TIME_ASSERT( k_nInitialTimestampMin < k_nInitialTimestamp ); +static std::atomic s_usecTimeOffset( k_nInitialTimestamp ); + +// Max delta allowed between GetLocalTimestamp calls before we clamp. +// Matches the service thread's max poll interval (k_msMaxPollWait * 1100us). +constexpr SteamNetworkingMicroseconds k_usecMaxTimestampDelta = 1100 * 1000; + +// Defined in steamnetworkingsockets_socketthread.cpp +extern std::atomic s_nLowLevelSupportRefCount; + +} // namespace SteamNetworkingSocketsLib + +SteamNetworkingMicroseconds SteamNetworkingSockets_GetLocalTimestamp() +{ + SteamNetworkingMicroseconds usecResult; + long long usecLastReturned; + for (;;) + { + // Fetch values into locals (probably registers) + usecLastReturned = SteamNetworkingSocketsLib::s_usecTimeLastReturned; + long long usecOffset = SteamNetworkingSocketsLib::s_usecTimeOffset; + + // Read raw timer + uint64 usecRaw = Plat_USTime(); + + // Add offset to get value in "SteamNetworkingMicroseconds" time + usecResult = usecRaw + usecOffset; + + // How much raw timer time (presumed to be wall clock time) has elapsed since + // we read the timer? + SteamNetworkingMicroseconds usecElapsed = usecResult - usecLastReturned; + Assert( usecElapsed >= 0 ); // Our raw timer function is not monotonic! We assume this never happens! + if ( usecElapsed <= SteamNetworkingSocketsLib::k_usecMaxTimestampDelta ) + { + // Should be the common case - only a relatively small of time has elapsed + break; + } + if ( SteamNetworkingSocketsLib::s_nLowLevelSupportRefCount.load(std::memory_order_acquire) <= 0 ) + { + // We don't have any expectation that we should be updating the timer frequently, + // so a big jump in the value just means they aren't calling it very often + break; + } + + // NOTE: We should only rarely get here, and probably as a result of running under the debugger + + // Adjust offset so that delta between timestamps is limited + long long usecNewOffset = usecOffset - ( usecElapsed - SteamNetworkingSocketsLib::k_usecMaxTimestampDelta ); + usecResult = usecRaw + usecNewOffset; + + // Save the new offset. + if ( SteamNetworkingSocketsLib::s_usecTimeOffset.compare_exchange_strong( usecOffset, usecNewOffset ) ) + break; + + // Race condition which should be extremely rare. Some other thread changed the offset, in the time + // between when we fetched it and now. (So, a really small race window!) Just start all over from + // the beginning. + } + + // Save the last value returned. Unless another thread snuck in there while we were busy. + // If so, that's OK. + SteamNetworkingSocketsLib::s_usecTimeLastReturned.compare_exchange_strong( usecLastReturned, usecResult ); + + return usecResult; +} + +///////////////////////////////////////////////////////////////////////////// +// +// memory override +// +///////////////////////////////////////////////////////////////////////////// + +#include + +#ifdef STEAMNETWORKINGSOCKETS_ENABLE_MEM_OVERRIDE + +namespace SteamNetworkingSocketsLib { +static bool s_bHasAllocatedMemory = false; + +static void* (*s_pfn_malloc)( size_t s ) = malloc; +static void (*s_pfn_free)( void *p ) = free; +static void* (*s_pfn_realloc)( void *p, size_t s ) = realloc; +} + +void *SteamNetworkingSockets_Malloc( size_t s ) +{ + s_bHasAllocatedMemory = true; + return (*s_pfn_malloc)( s ); +} + +void *SteamNetworkingSockets_Realloc( void *p, size_t s ) +{ + s_bHasAllocatedMemory = true; + return (*s_pfn_realloc)( p, s ); +} + +void SteamNetworkingSockets_Free( void *p ) +{ + (*s_pfn_free)( p ); +} + +STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_SetCustomMemoryAllocator( + void* (*pfn_malloc)( size_t s ), + void (*pfn_free)( void *p ), + void* (*pfn_realloc)( void *p, size_t s ) +) { + Assert( !s_bHasAllocatedMemory ); // Too late! + + s_pfn_malloc = pfn_malloc; + s_pfn_free = pfn_free; + s_pfn_realloc = pfn_realloc; +} +#endif diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_socketthread.cpp b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_socketthread.cpp index 0837b92..423c6b8 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_socketthread.cpp +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_socketthread.cpp @@ -1,14 +1,11 @@ //====== Copyright Valve Corporation, All rights reserved. ==================== // -// "Low level" stuff used by the SteamNetworkingSockets client library to -// interface with the operating system. Ideally, most OS-specific details are -// handled in this file. +// Socket and service thread management for SteamNetworkingSockets. // // - Dealing with OS sockets, sending/receiving of UDP packets // - Simulating network conditions such as fake lag/loss/reording/jitter // - Managing the main service thread, polling efficiently // - Dispatching received packets to the registered callbacks. -// - Lock (mutex) details, especially hygiene enforcement and debugging // - Support for wifi adapters that can send on both bands simultaneously // #include @@ -91,21 +88,7 @@ constexpr int k_cbETWEventUDPPacketDataSize = 16; namespace SteamNetworkingSocketsLib { -inline void ETW_LongOp( const char *opName, SteamNetworkingMicroseconds usec, const char *pszInfo ) -{ - if ( !pszInfo ) - pszInfo = ""; - TraceLoggingWrite( - HTraceLogging_SteamNetworkingSockets, - "LongOp", - TraceLoggingLevel( WINEVENT_LEVEL_WARNING ), - TraceLoggingUInt64( usec, "Microseconds" ), - TraceLoggingString( pszInfo, "ExtraInfo" ) - ); -} - constexpr int k_msMaxPollWait = 1000; -constexpr SteamNetworkingMicroseconds k_usecMaxTimestampDelta = k_msMaxPollWait * 1100; int g_cbUDPSocketBufferSize = 256*1024; @@ -113,415 +96,7 @@ int g_cbUDPSocketBufferSize = 256*1024; int g_nSendECNAuto = -1; #endif -/// Global lock for all local data structures -static Lock s_mutexGlobalLock( "global", 0, LockDebugInfo::k_nOrder_Global ); - -#if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 - -// By default, complain if we hold the lock for more than this long. -// TSan adds 10-20x overhead so the threshold is scaled up accordingly. -#ifdef __SANITIZE_THREAD__ -constexpr SteamNetworkingMicroseconds k_usecDefaultLongLockHeldWarningThreshold = 5*1000*20; -#else -constexpr SteamNetworkingMicroseconds k_usecDefaultLongLockHeldWarningThreshold = 5*1000; -#endif - -// Debug the locks active on the cu -struct ThreadLockDebugInfo -{ - static constexpr int k_nMaxHeldLocks = 8; - static constexpr int k_nMaxTags = 32; - - int m_nHeldLocks = 0; - int m_nTags = 0; - - SteamNetworkingMicroseconds m_usecLongLockWarningThreshold; - SteamNetworkingMicroseconds m_usecIgnoreLongLockWaitTimeUntil; - SteamNetworkingMicroseconds m_usecOuterLockStartTime; // Time when we started waiting on outermost lock (if we don't have it yet), or when we aquired the lock (if we have it) - - const LockDebugInfo *m_arHeldLocks[ k_nMaxHeldLocks ]; - struct Tag_t - { - const char *m_pszTag; - int m_nCount; - }; - Tag_t m_arTags[ k_nMaxTags ]; - - inline void AddTag( const char *pszTag ); -}; - -static void (*s_fLockAcquiredCallback)( const char *tags, SteamNetworkingMicroseconds usecWaited ); -static void (*s_fLockHeldCallback)( const char *tags, SteamNetworkingMicroseconds usecWaited ); - -// Threshold for warning about waiting on any lock. -#ifdef __SANITIZE_THREAD__ -static SteamNetworkingMicroseconds s_usecLockWaitWarningThreshold = 300*1000; -#else -static SteamNetworkingMicroseconds s_usecLockWaitWarningThreshold = 2*1000; -#endif - -// Threshold for the service-thread-specific *assert* -#ifdef __SANITIZE_THREAD__ -static SteamNetworkingMicroseconds s_usecServiceThreadLockWaitWarning = 300*1000; -#else -static SteamNetworkingMicroseconds s_usecServiceThreadLockWaitWarning = 50*1000; -#endif - -/// Get the per-thread debug info -static ThreadLockDebugInfo &GetThreadDebugInfo() -{ - // Apple seems to hate thread_local. Is there some sort of feature - // define a can check here? It's a shame because it's really very - // efficient on MSVC, gcc, and clang on Windows and linux. - // - // Apple seems to support thread_local starting with Xcode 8.0 - #if defined(__APPLE__) && __clang_major__ < 8 - - static pthread_key_t key; - static pthread_once_t key_once = PTHREAD_ONCE_INIT; - - // One time init the TLS key - pthread_once( &key_once, - [](){ // Initialization code to run once - pthread_key_create( - &key, - [](void *ptr) { free(ptr); } // Destructor function - ); - } - ); - - // Get current object - void *result = pthread_getspecific(key); - if ( unlikely( result == nullptr ) ) - { - result = malloc( sizeof(ThreadLockDebugInfo) ); - memset( result, 0, sizeof(ThreadLockDebugInfo) ); - pthread_setspecific(key, result); - } - return *static_cast( result ); - #else - - // Use thread_local - thread_local ThreadLockDebugInfo tls_lockDebugInfo; - return tls_lockDebugInfo; - #endif -} - -/// If non-NULL, add a "tag" to the lock journal for the current thread. -/// This is useful so that if we hold a lock for a long time, we can get -/// an idea what sorts of operations were taking a long time. -inline void ThreadLockDebugInfo::AddTag( const char *pszTag ) -{ - if ( !pszTag ) - return; - - Assert( m_nHeldLocks > 0 ); // Can't add a tag unless we are locked! - - for ( int i = 0 ; i < m_nTags ; ++i ) - { - if ( m_arTags[i].m_pszTag == pszTag ) - { - ++m_arTags[i].m_nCount; - return; - } - } - - if ( m_nTags >= ThreadLockDebugInfo::k_nMaxTags ) - return; - - m_arTags[ m_nTags ].m_pszTag = pszTag; - m_arTags[ m_nTags ].m_nCount = 1; - ++m_nTags; -} - -LockDebugInfo::~LockDebugInfo() -{ - // We should not be locked! If we are, remove us - ThreadLockDebugInfo &t = GetThreadDebugInfo(); - for ( int i = t.m_nHeldLocks-1 ; i >= 0 ; --i ) - { - if ( t.m_arHeldLocks[i] == this ) - { - AssertMsg( false, "Lock '%s' being destroyed while it is held!", m_pszName ); - AboutToUnlock(); - } - } -} - -void LockDebugInfo::AboutToLock( bool bTry ) -{ - ThreadLockDebugInfo &t = GetThreadDebugInfo(); - - // First lock held by this thread? - if ( t.m_nHeldLocks == 0 ) - { - // Remember when we started trying to lock - t.m_usecOuterLockStartTime = SteamNetworkingSockets_GetLocalTimestamp(); - return; - } - - // We already hold a lock. Check for taking locks in such a way - // that might lead to deadlocks. - const LockDebugInfo *pTopLock = t.m_arHeldLocks[ t.m_nHeldLocks-1 ]; - - // Taking locks in increasing order is always allowed - if ( likely( pTopLock->m_nOrder < m_nOrder ) ) - return; - - // Global lock *must* always be the outermost lock. (It is legal to take other locks in - // between and then lock the global lock recursively.) - const bool bHoldGlobalLock = t.m_arHeldLocks[ 0 ] == &s_mutexGlobalLock; - AssertMsg( - bHoldGlobalLock || this != &s_mutexGlobalLock, - "Taking global lock while already holding lock '%s'", t.m_arHeldLocks[ 0 ]->m_pszName - ); - - // If they are only "trying", we allow out-of-order behaviour. - if ( bTry ) - return; - - // It's always OK to lock recursively. - // - // (Except for "short duration" locks, which are allowed to - // use a mutex implementation that does not support this.) - if ( !( m_nFlags & k_nFlag_ShortDuration ) ) - { - for ( int i = 0 ; i < t.m_nHeldLocks ; ++i ) - { - if ( t.m_arHeldLocks[i] == this ) - return; - } - } - - // Taking multiple object locks? This is allowed under certain circumstances - if ( likely( pTopLock->m_nOrder == m_nOrder && m_nOrder == k_nOrder_ObjectOrTable ) ) - { - - // If we hold the global lock, it's OK - if ( bHoldGlobalLock ) - return; - - // If the global lock isn't held, then no more than one - // object lock is allowed, since two different threads - // might take them in different order. - constexpr int k_nObjectFlags = LockDebugInfo::k_nFlag_Connection | LockDebugInfo::k_nFlag_PollGroup; - if ( - ( ( m_nFlags & k_nObjectFlags ) != 0 ) - //|| ( m_nFlags & k_nFlag_Table ) // We actually do this in one place when we know it's OK. Not worth it right now to get this situation exempted from the checking. - ) { - // We must not already hold any existing object locks (except perhaps this one) - for ( int i = 0 ; i < t.m_nHeldLocks ; ++i ) - { - const LockDebugInfo *pOtherLock = t.m_arHeldLocks[ i ]; - AssertMsg( pOtherLock == this || !( pOtherLock->m_nFlags & k_nObjectFlags ), - "Taking lock '%s' and then '%s', while not holding the global lock", pOtherLock->m_pszName, m_pszName ); - } - } - - // Usage is OK if we didn't find any problems above - return; - } - - AssertMsg( false, "Taking lock '%s' while already holding lock '%s'", m_pszName, pTopLock->m_pszName ); -} - -void LockDebugInfo::OnLocked( const char *pszTag ) -{ - ThreadLockDebugInfo &t = GetThreadDebugInfo(); - - Assert( t.m_nHeldLocks < ThreadLockDebugInfo::k_nMaxHeldLocks ); - t.m_arHeldLocks[ t.m_nHeldLocks++ ] = this; - - if ( t.m_nHeldLocks == 1 ) - { - SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp(); - SteamNetworkingMicroseconds usecTimeSpentWaitingOnLock = usecNow - t.m_usecOuterLockStartTime; - t.m_usecLongLockWarningThreshold = k_usecDefaultLongLockHeldWarningThreshold; - t.m_nTags = 0; - - if ( usecTimeSpentWaitingOnLock > s_usecLockWaitWarningThreshold && usecNow > t.m_usecIgnoreLongLockWaitTimeUntil ) - { - if ( pszTag ) - SpewWarning( "Waited %.1fms for SteamNetworkingSockets lock [%s]", usecTimeSpentWaitingOnLock*1e-3, pszTag ); - else - SpewWarning( "Waited %.1fms for SteamNetworkingSockets lock", usecTimeSpentWaitingOnLock*1e-3 ); - ETW_LongOp( "lock wait", usecTimeSpentWaitingOnLock, pszTag ); - } - - auto callback = s_fLockAcquiredCallback; // save to temp, to prevent very narrow race condition where variable is cleared after we null check it, and we call null - if ( callback ) - callback( pszTag, usecTimeSpentWaitingOnLock ); - - t.m_usecOuterLockStartTime = usecNow; - } - - t.AddTag( pszTag ); -} - -void LockDebugInfo::AboutToUnlock() -{ - char tags[ 256 ]; - - SteamNetworkingMicroseconds usecElapsed = 0; - SteamNetworkingMicroseconds usecElapsedTooLong = 0; - auto lockHeldCallback = s_fLockHeldCallback; - - ThreadLockDebugInfo &t = GetThreadDebugInfo(); - Assert( t.m_nHeldLocks > 0 ); - - // Unlocking the last lock? - if ( t.m_nHeldLocks == 1 ) - { - - // We're about to do the final release. How long did we hold the lock? - usecElapsed = SteamNetworkingSockets_GetLocalTimestamp() - t.m_usecOuterLockStartTime; - - // Too long? We need to check the threshold here because the threshold could - // change by another thread immediately after we release the lock. Also, if - // we're debugging, all bets are off. They could have hit a breakpoint, and - // we don't want to create a bunch of confusing spew with spurious asserts - if ( usecElapsed >= t.m_usecLongLockWarningThreshold && !Plat_IsInDebugSession() ) - { - usecElapsedTooLong = usecElapsed; - } - - if ( usecElapsedTooLong > 0 || lockHeldCallback ) - { - char *p = tags; - char *end = tags + sizeof(tags) - 1; - for ( int i = 0 ; i < t.m_nTags && p+5 < end ; ++i ) - { - if ( p > tags ) - *(p++) = ','; - - const ThreadLockDebugInfo::Tag_t &tag = t.m_arTags[i]; - int taglen = std::min( int(end-p), (int)V_strlen( tag.m_pszTag ) ); - memcpy( p, tag.m_pszTag, taglen ); - p += taglen; - - if ( tag.m_nCount > 1 ) - { - int l = end-p; - if ( l <= 5 ) - break; - p += V_snprintf( p, l, "(x%d)", tag.m_nCount ); - } - } - *p = '\0'; - } - - t.m_nTags = 0; - t.m_usecOuterLockStartTime = 0; // Just for grins. - } - - if ( usecElapsed > 0 && lockHeldCallback ) - { - lockHeldCallback(tags, usecElapsed); - } - - // Yelp if we held the lock for longer than the threshold. - if ( usecElapsedTooLong != 0 ) - { - SpewWarning( - "SteamNetworkingSockets lock held for %.1fms. (Performance warning.) %s\n" - "This is usually a symptom of a general performance problem such as thread starvation.", - usecElapsedTooLong*1e-3, tags - ); - ETW_LongOp( "lock held", usecElapsedTooLong, tags ); - } - - // NOTE: We are allowed to unlock out of order! We specifically - // do this with the table lock! - for ( int i = t.m_nHeldLocks-1 ; i >= 0 ; --i ) - { - if ( t.m_arHeldLocks[i] == this ) - { - --t.m_nHeldLocks; - if ( i < t.m_nHeldLocks ) // Don't do the memmove in the common case of stack pop - memmove( &t.m_arHeldLocks[i], &t.m_arHeldLocks[i+1], (t.m_nHeldLocks-i) * sizeof(t.m_arHeldLocks[0]) ); - t.m_arHeldLocks[t.m_nHeldLocks] = nullptr; // Just for grins - return; - } - } - - AssertMsg( false, "Unlocked a lock '%s' that wasn't held?", m_pszName ); -} - -void LockDebugInfo::_AssertHeldByCurrentThread( const char *pszFile, int line, const char *pszTag ) const -{ - ThreadLockDebugInfo &t = GetThreadDebugInfo(); - for ( int i = t.m_nHeldLocks-1 ; i >= 0 ; --i ) - { - if ( t.m_arHeldLocks[i] == this ) - { - t.AddTag( pszTag ); - return; - } - } - - AssertMsg( false, "%s(%d): Lock '%s' not held", pszFile, line, m_pszName ); -} - -void SteamNetworkingGlobalLock::SetLongLockWarningThresholdMS( const char *pszTag, int msWarningThreshold ) -{ - AssertHeldByCurrentThread( pszTag ); - SteamNetworkingMicroseconds usecWarningThreshold = SteamNetworkingMicroseconds{msWarningThreshold}*1000; - ThreadLockDebugInfo &t = GetThreadDebugInfo(); - if ( t.m_usecLongLockWarningThreshold < usecWarningThreshold ) - { - t.m_usecLongLockWarningThreshold = usecWarningThreshold; - t.m_usecIgnoreLongLockWaitTimeUntil = SteamNetworkingSockets_GetLocalTimestamp() + t.m_usecLongLockWarningThreshold; - } -} - -void SteamNetworkingGlobalLock::_AssertHeldByCurrentThread( const char *pszFile, int line ) -{ - s_mutexGlobalLock._AssertHeldByCurrentThread( pszFile, line, nullptr ); -} - -void SteamNetworkingGlobalLock::_AssertHeldByCurrentThread( const char *pszFile, int line, const char *pszTag ) -{ - s_mutexGlobalLock._AssertHeldByCurrentThread( pszFile, line, pszTag ); -} - -#endif // #if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 - -void SteamNetworkingGlobalLock::Lock( const char *pszTag ) -{ - s_mutexGlobalLock.lock( pszTag ); -} - -bool SteamNetworkingGlobalLock::TryLock( const char *pszTag, int msTimeout ) -{ - return s_mutexGlobalLock.try_lock_for( msTimeout, pszTag ); -} - -void SteamNetworkingGlobalLock::Unlock() -{ - s_mutexGlobalLock.unlock(); -} - -static void SeedWeakRandomGenerator() -{ - - // Seed cheesy random number generator using true source of entropy - int temp; - CCrypto::GenerateRandomBlock( &temp, sizeof(temp) ); - WeakRandomSeed( temp ); -} - -static std::atomic s_usecTimeLastReturned; - -// Start with an offset so that a timestamp of zero is always pretty far in the past. -// But round it up to nice round number, so that looking at timestamps in the debugger -// is easy to read. -const long long k_nInitialTimestampMin = k_nMillion*24*3600*30; -const long long k_nInitialTimestamp = 3000000000000ll; -COMPILE_TIME_ASSERT( 2000000000000ll < k_nInitialTimestampMin ); -COMPILE_TIME_ASSERT( k_nInitialTimestampMin < k_nInitialTimestamp ); -static std::atomic s_usecTimeOffset( k_nInitialTimestamp ); - -static std::atomic s_nLowLevelSupportRefCount(0); +std::atomic s_nLowLevelSupportRefCount(0); static volatile bool s_bManualPollMode; // Try to guess if the route the specified address is probably "local". @@ -2129,14 +1704,6 @@ IRawUDPSocket *OpenRawUDPSocket( CRecvPacketCallback callback, SteamNetworkingEr return OpenRawUDPSocketInternal( callback, errMsg, pAddrLocal, pnAddressFamilies ); } -static inline void AssertGlobalLockHeldExactlyOnce() -{ - #if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 - ThreadLockDebugInfo &t = GetThreadDebugInfo(); - Assert( t.m_nHeldLocks == 1 && t.m_arHeldLocks[0] == &s_mutexGlobalLock ); - #endif -} - /// Draw one specific UDP socket. Returns false if we detect a /// global shutdown attempt and abort static bool DrainSocket( CRawUDPSocketImpl *pSock ) @@ -3814,60 +3381,6 @@ void SteamNetworkingSocketsLowLevelValidate( CValidator &validator ) } #endif -SteamNetworkingMicroseconds SteamNetworkingSockets_GetLocalTimestamp() -{ - SteamNetworkingMicroseconds usecResult; - long long usecLastReturned; - for (;;) - { - // Fetch values into locals (probably registers) - usecLastReturned = SteamNetworkingSocketsLib::s_usecTimeLastReturned; - long long usecOffset = SteamNetworkingSocketsLib::s_usecTimeOffset; - - // Read raw timer - uint64 usecRaw = Plat_USTime(); - - // Add offset to get value in "SteamNetworkingMicroseconds" time - usecResult = usecRaw + usecOffset; - - // How much raw timer time (presumed to be wall clock time) has elapsed since - // we read the timer? - SteamNetworkingMicroseconds usecElapsed = usecResult - usecLastReturned; - Assert( usecElapsed >= 0 ); // Our raw timer function is not monotonic! We assume this never happens! - if ( usecElapsed <= k_usecMaxTimestampDelta ) - { - // Should be the common case - only a relatively small of time has elapsed - break; - } - if ( SteamNetworkingSocketsLib::s_nLowLevelSupportRefCount.load(std::memory_order_acquire) <= 0 ) - { - // We don't have any expectation that we should be updating the timer frequently, - // so a big jump in the value just means they aren't calling it very often - break; - } - - // NOTE: We should only rarely get here, and probably as a result of running under the debugger - - // Adjust offset so that delta between timestamps is limited - long long usecNewOffset = usecOffset - ( usecElapsed - k_usecMaxTimestampDelta ); - usecResult = usecRaw + usecNewOffset; - - // Save the new offset. - if ( SteamNetworkingSocketsLib::s_usecTimeOffset.compare_exchange_strong( usecOffset, usecNewOffset ) ) - break; - - // Race condition which should be extremely rare. Some other thread changed the offset, in the time - // between when we fetched it and now. (So, a really small race window!) Just start all over from - // the beginning. - } - - // Save the last value returned. Unless another thread snuck in there while we were busy. - // If so, that's OK. - SteamNetworkingSocketsLib::s_usecTimeLastReturned.compare_exchange_strong( usecLastReturned, usecResult ); - - return usecResult; -} - bool ResolveHostname( const char* pszHostname, CUtlVector< SteamNetworkingIPAddr > *pAddrs ) { #ifdef STEAMNETWORKINGSOCKETS_ENABLE_RESOLVEHOSTNAME @@ -4141,80 +3654,8 @@ STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_Poll( int msMaxWait SteamNetworkingGlobalLock::Unlock(); } -STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_SetLockWaitWarningThreshold( SteamNetworkingMicroseconds usecTheshold ) -{ - #if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 - s_usecLockWaitWarningThreshold = usecTheshold; - #endif - s_usecServiceThreadLockWaitWarning = usecTheshold; -} - -STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_SetLockAcquiredCallback( void (*callback)( const char *tags, SteamNetworkingMicroseconds usecWaited ) ) -{ - #if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 - s_fLockAcquiredCallback = callback; - #else - // Should we assert here? - #endif -} - -STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_SetLockHeldCallback( void (*callback)( const char *tags, SteamNetworkingMicroseconds usecWaited ) ) -{ - #if STEAMNETWORKINGSOCKETS_LOCK_DEBUG_LEVEL > 0 - s_fLockHeldCallback = callback; - #else - // Should we assert here? - #endif -} - STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_SetServiceThreadInitCallback( void (*callback)() ) { AssertMsg( !IsServiceThreadRunning(), "Too late!" ); s_fnServiceThreadInitCallback = callback; } - -///////////////////////////////////////////////////////////////////////////// -// -// memory override -// -///////////////////////////////////////////////////////////////////////////// - -#include - -#ifdef STEAMNETWORKINGSOCKETS_ENABLE_MEM_OVERRIDE - -static bool s_bHasAllocatedMemory = false; - -static void* (*s_pfn_malloc)( size_t s ) = malloc; -static void (*s_pfn_free)( void *p ) = free; -static void* (*s_pfn_realloc)( void *p, size_t s ) = realloc; - -void *SteamNetworkingSockets_Malloc( size_t s ) -{ - s_bHasAllocatedMemory = true; - return (*s_pfn_malloc)( s ); -} - -void *SteamNetworkingSockets_Realloc( void *p, size_t s ) -{ - s_bHasAllocatedMemory = true; - return (*s_pfn_realloc)( p, s ); -} - -void SteamNetworkingSockets_Free( void *p ) -{ - (*s_pfn_free)( p ); -} - -STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingSockets_SetCustomMemoryAllocator( - void* (*pfn_malloc)( size_t s ), - void (*pfn_free)( void *p ), - void* (*pfn_realloc)( void *p, size_t s ) -) { - Assert( !s_bHasAllocatedMemory ); // Too late! - - s_pfn_malloc = pfn_malloc; - s_pfn_free = pfn_free; - s_pfn_realloc = pfn_realloc; -} -#endif