diff --git a/server/TracyLocks.cpp b/server/TracyLocks.cpp index ee4b97ba..eda6b44a 100644 --- a/server/TracyLocks.cpp +++ b/server/TracyLocks.cpp @@ -583,4 +583,62 @@ void DetectLockDeadlocks( const unordered_flat_map& lockMap, DetectLockDeadlocksImpl( lockMap, &candidates, groups, members ); } +static bool ThreadSetSubset( const Vector& sup, uint32_t sf, uint32_t sc, + const Vector& sub, uint32_t bf, uint32_t bc ) +{ + if( bc > sc ) return false; + uint32_t j = 0; + for( uint32_t i=0; i& groups, Vector& members, + const Vector& found, const Vector& foundMembers ) +{ + for( const auto& g : found ) + { + Vector drop; + bool duplicate = false; + for( uint32_t si=0; si keptGroups; + Vector keptMembers; + for( uint32_t si=0; si& lockMap, void DetectLockDeadlocks( const unordered_flat_map& lockMap, const unordered_flat_set& candidates, Vector& groups, Vector& members ); + +// Reconcile a detection pass against previously reported groups. A found group equal +// to a stored one is a repeat of the still-formed cycle and is skipped. A found group +// containing stored ones absorbs them: cycle members are frozen and can never +// fragment, so a later pass can only regroup reported threads into larger sets. +void MergeDetectedDeadlocks( Vector& groups, Vector& members, + const Vector& found, const Vector& foundMembers ); + } #endif diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index 673e9bd5..fbc46a76 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -1623,6 +1623,8 @@ Worker::Worker( FileRead& f, EventType::Type eventMask, bool bgTasks, bool allow } } + DetectDeadlocks(); + s_loadProgress.total.store( 0, std::memory_order_relaxed ); m_loadTime = std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - loadStart ).count(); @@ -3653,6 +3655,40 @@ void Worker::AppendLock( LockMap& lock, int64_t time, uint16_t slot, LockEvent:: AppendLockEvent( lock, time, slot, type ); } +bool Worker::IsDeadlockedThread( uint64_t thread ) const +{ + for( auto& m : m_data.deadlockMembers ) + { + if( m.thread == thread ) return true; + } + return false; +} + +bool Worker::IsDeadlockedPair( uint64_t thread, uint32_t lock ) const +{ + for( auto& m : m_data.deadlockMembers ) + { + if( m.thread == thread && m.lock == lock ) return true; + } + return false; +} + +void Worker::DetectDeadlocks() +{ + Vector found; + Vector foundMembers; + DetectLockDeadlocks( m_data.lockMap, found, foundMembers ); + MergeDetectedDeadlocks( m_data.deadlockGroups, m_data.deadlockMembers, found, foundMembers ); +} + +void Worker::DetectDeadlocksLive() +{ + Vector found; + Vector foundMembers; + DetectLockDeadlocks( m_data.lockMap, m_data.waitingLocks, found, foundMembers ); + MergeDetectedDeadlocks( m_data.deadlockGroups, m_data.deadlockMembers, found, foundMembers ); +} + bool Worker::CheckString( uint64_t ptr ) { if( ptr == 0 ) return true; @@ -5554,6 +5590,13 @@ void Worker::ProcessLockThreadEvent( uint64_t id, int64_t time, uint64_t thread, } td->inLocks = 1; AppendLock( lock, lt, slot, type ); + if( lock.curWaitCount + lock.curWaitSharedCount != 0 ) m_data.waitingLocks.insert( ( uint32_t )id ); + else m_data.waitingLocks.erase( ( uint32_t )id ); + if( type == LockEvent::Type::Wait || type == LockEvent::Type::WaitShared || + type == LockEvent::Type::Obtain || type == LockEvent::Type::ObtainShared ) + { + m_detectDeadlocks = true; + } } void Worker::ProcessLockWait( const QueueLockWait& ev ) diff --git a/server/TracyWorker.hpp b/server/TracyWorker.hpp index 5499e43e..f98ab987 100644 --- a/server/TracyWorker.hpp +++ b/server/TracyWorker.hpp @@ -386,6 +386,10 @@ private: CrashEvent crashEvent; + Vector deadlockGroups; + Vector deadlockMembers; + unordered_flat_set waitingLocks; + unordered_flat_map ctxSwitch; CpuData cpuData[256]; @@ -520,6 +524,13 @@ public: }; MainThreadDataLockGuard ObtainLockForMainThread() { return { m_data }; } + void UpdateDeadlocks() + { + if( !m_detectDeadlocks ) return; + m_detectDeadlocks = false; + if( !m_data.waitingLocks.empty() ) DetectDeadlocksLive(); + } + size_t GetFrameCount( const FrameData& fd ) const { return fd.frames.size(); } size_t GetFullFrameCount( const FrameData& fd ) const; bool AreFramesUsed() const; @@ -618,6 +629,10 @@ public: #endif const CrashEvent& GetCrashEvent() const { return m_data.crashEvent; } + const Vector& GetDeadlockGroups() const { return m_data.deadlockGroups; } + const Vector& GetDeadlockMembers() const { return m_data.deadlockMembers; } + bool IsDeadlockedThread( uint64_t thread ) const; + bool IsDeadlockedPair( uint64_t thread, uint32_t lock ) const; // Some zones may have incomplete timing data (only start time is available, end hasn't arrived yet). // GetZoneEnd() will try to infer the end time by looking at child zones (parent zone can't end @@ -973,6 +988,8 @@ private: void AppendLock( LockMap& lock, int64_t time, uint16_t slot, LockEvent::Type type ); void ProcessLockThreadEvent( uint64_t id, int64_t time, uint64_t thread, LockEvent::Type type ); + void DetectDeadlocks(); + void DetectDeadlocksLive(); bool CheckString( uint64_t ptr ); void CheckThreadString( uint64_t id ); @@ -1100,6 +1117,8 @@ private: bool m_terminate = false; bool m_crashed = false; bool m_disconnect = false; + + bool m_detectDeadlocks = false; void* m_stream; // LZ4_streamDecode_t* char* m_buffer; int m_bufferOffset;