Break out a few misc things from steamnetworkingsockets_socketthread.cpp

Lock debugging
Timer
Memory override
This commit is contained in:
Fletcher Dunn
2026-05-11 18:27:52 -07:00
parent 72e239af38
commit 5c28eac0eb
4 changed files with 643 additions and 584 deletions
+1
View File
@@ -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"
@@ -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<ShortDurationLock>;
#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
@@ -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 <vstdlib/random.h>
#include <tier0/valve_tracelogging.h>
#if IsPosix()
#include <pthread.h>
#endif
#include <tier0/memdbgon.h>
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<RecursiveTimedMutexImpl> 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<ThreadLockDebugInfo *>( 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<long long> 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<long long> 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<int> 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 <tier0/memdbgoff.h>
#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
@@ -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 <thread>
@@ -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<RecursiveTimedMutexImpl> 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<ThreadLockDebugInfo *>( 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<long long> 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<long long> s_usecTimeOffset( k_nInitialTimestamp );
static std::atomic<int> s_nLowLevelSupportRefCount(0);
std::atomic<int> 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 <tier0/memdbgoff.h>
#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