Files
ThordekkCore/DEADLOCK_DEBUGGING_GUIDE.md
T
2026-01-20 21:28:45 -03:00

658 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# COMPREHENSIVE DEADLOCK DEBUGGING GUIDE
## TrinityCore PlayerBot Module - Visual Studio Enterprise
## TABLE OF CONTENTS
1. [Current Issues](#current-issues)
2. [Root Cause Analysis](#root-cause-analysis)
3. [Enhanced Logging System](#enhanced-logging-system)
4. [Visual Studio Enterprise Debugging](#visual-studio-enterprise-debugging)
5. [Automated Debugging Tools](#automated-debugging-tools)
6. [Configuration & Usage](#configuration--usage)
7. [Advanced Techniques](#advanced-techniques)
---
## CURRENT ISSUES
### Problem: False "DEADLOCK DETECTED" Warnings
**Current Logs**:
```
🔧 DEADLOCK DETECTED: Future 10 of 10 (bot Player-1-00000033) did not complete after 0 seconds!
🔧 DEADLOCK DETECTED: Future 6 of 9 (bot Player-1-00000036) did not complete after 0 seconds!
```
**Root Causes Identified**:
1. **Integer Division Truncation** (BotWorldSessionMgr.cpp:770):
```cpp
TC_LOG_FATAL("module.playerbot.session",
"🔧 DEADLOCK DETECTED: Future {} of {} (bot {}) did not complete after {} seconds!",
i + 1, futures.size(), futureGuids[i].ToString(),
(MAX_RETRIES * 100) / 1000); // ← (5 * 100) / 1000 = 500 / 1000 = 0 (integer truncation!)
```
**Fixed Calculation**:
```cpp
static_cast<double>(MAX_RETRIES * 100) / 1000.0 // = 0.5 seconds
```
2. **Timeout Too Short for High Load**:
- Current: 500ms (5 retries × 100ms)
- Problem: Under heavy load (100+ bots), thread pool may legitimately need >500ms
- Solution: Adaptive timeout based on system load
3. **No Call Stack Information**:
- Can't determine WHERE the thread is blocked
- Can't identify WHAT mutex/resource is causing the wait
- Can't trace execution path leading to deadlock
---
## ROOT CAUSE ANALYSIS
### What's Actually Happening
**BotWorldSessionMgr::Update()** flow (lines 720-772):
1. **Submit bot updates to thread pool** (futures created)
2. **Wait for futures with timeout loop**:
```cpp
for (size_t i = 0; i < futures.size(); ++i)
{
uint32 retries = 0;
constexpr uint32 MAX_RETRIES = 5; // 5 × 100ms = 500ms
while (!completed && retries < MAX_RETRIES)
{
auto status = future.wait_for(std::chrono::milliseconds(100));
if (status == std::future_status::ready)
completed = true;
else
++retries; // ← Timeout, retry
}
if (!completed)
LOG_FATAL("DEADLOCK DETECTED"); // ← FALSE POSITIVE!
}
```
3. **Problem**: NOT a real deadlock - just slow thread pool!
- Thread pool has limited workers (e.g., 8 threads)
- 100 bot updates submitted simultaneously
- Some futures wait in queue for worker availability
- Worker finishes in 600ms (normal for heavy AI update)
- Timeout triggers at 500ms → false "DEADLOCK"
### Actual Deadlocks vs False Positives
**Real Deadlock Indicators**:
- Threads waiting on each other cyclically
- Mutex A held while waiting for Mutex B, vice versa
- Future never completes even after 10+ seconds
- CPU usage drops to near-zero (all threads blocked)
**False Positive Indicators** (current issue):
- Futures complete after timeout expires
- System under heavy load (100+ bots)
- CPU usage normal (threads actively processing)
- Warnings appear in bursts during update spikes
---
## ENHANCED LOGGING SYSTEM
### Fix 1: Correct Time Display
**File**: `src/modules/Playerbot/Session/BotWorldSessionMgr.cpp`
**Current (Line 770)**:
```cpp
TC_LOG_FATAL("module.playerbot.session",
"🔧 DEADLOCK DETECTED: Future {} of {} (bot {}) did not complete after {} seconds!",
i + 1, futures.size(), futureGuids[i].ToString(),
(MAX_RETRIES * 100) / 1000); // ← WRONG: Integer truncation → 0
```
**Enhanced**:
```cpp
TC_LOG_FATAL("module.playerbot.session",
"⚠️ POTENTIAL DEADLOCK: Future {}/{} (bot {}) timeout after {:.1f}s "
"(retries: {}, timeout: {}ms, threadPool: {} active/{} total threads) "
"📍 Capture call stack with Visual Studio!",
i + 1, futures.size(), futureGuids[i].ToString(),
static_cast<double>(MAX_RETRIES * 100) / 1000.0, // ← FIXED: 0.5 seconds
MAX_RETRIES,
MAX_RETRIES * 100,
Performance::GetThreadPool().GetActiveThreadCount(),
Performance::GetThreadPool().GetTotalThreadCount());
// ADD: Diagnostic information
TC_LOG_ERROR("module.playerbot.session.deadlock",
"📊 System State at Timeout:\n"
" Bot GUID: {}\n"
" Future Index: {}/{}\n"
" Total Wait Time: {}ms\n"
" Thread Pool Queue Depth: {}\n"
" Active Workers: {}/{}\n"
" System Load: {:.1f}%\n"
" Timestamp: {}",
futureGuids[i].ToString(),
i + 1, futures.size(),
MAX_RETRIES * 100,
Performance::GetThreadPool().GetQueuedTaskCount(),
Performance::GetThreadPool().GetActiveThreadCount(),
Performance::GetThreadPool().GetTotalThreadCount(),
Performance::GetThreadPool().GetLoadPercentage(),
std::chrono::system_clock::now());
```
### Fix 2: Add Thread State Logging
**Before timeout loop** (add at line 722):
```cpp
// Diagnostic: Log thread pool state before waiting
TC_LOG_INFO("module.playerbot.session.futures",
"⏳ Waiting for {} futures (current threadPool: {} active/{} total, {} queued tasks)",
futures.size(),
Performance::GetThreadPool().GetActiveThreadCount(),
Performance::GetThreadPool().GetTotalThreadCount(),
Performance::GetThreadPool().GetQueuedTaskCount());
```
### Fix 3: Per-Future Progress Logging
**Inside retry loop** (modify line 756):
```cpp
if (retries % 2 == 0) // Every 200ms
{
TC_LOG_WARN("module.playerbot.session.futures",
"⏱️ Future {}/{} (bot {}) waiting for {}ms (attempt {}/{}) - "
"ThreadPool: {} active, {} queued, Load: {:.1f}% - "
"🔍 TIP: Attach Visual Studio debugger now!",
i + 1, futures.size(),
futureGuids[i].ToString(),
retries * 100,
retries, MAX_RETRIES,
Performance::GetThreadPool().GetActiveThreadCount(),
Performance::GetThreadPool().GetQueuedTaskCount(),
Performance::GetThreadPool().GetLoadPercentage());
}
```
### Fix 4: Successful Completion Logging
**After future.get()** (add at line 740):
```cpp
future.get(); // Get result + exception propagation
TC_LOG_DEBUG("module.playerbot.session.futures",
"✅ Future {}/{} (bot {}) completed successfully after {}ms",
i + 1, futures.size(),
futureGuids[i].ToString(),
retries * 100);
```
---
## VISUAL STUDIO ENTERPRISE DEBUGGING
### Method 1: Attach to Running Process
#### Step 1: Enable Just-In-Time Debugging
1. Open Visual Studio Enterprise
2. **Tools → Options → Debugging → Just-In-Time**
3. Enable: ☑ Managed ☑ Native ☑ Script
4. Click **OK**
#### Step 2: Attach to worldserver.exe
1. Launch worldserver.exe (RelWithDebInfo or Debug build)
2. In Visual Studio: **Debug → Attach to Process** (Ctrl+Alt+P)
3. Filter: `worldserver`
4. Select `worldserver.exe`
5. **Attach Type**: Native Only
6. Click **Attach**
#### Step 3: Set Conditional Breakpoints
**Location**: `BotWorldSessionMgr.cpp:768` (DEADLOCK DETECTED line)
**Conditional Breakpoint**:
```cpp
retries >= 3 // Break only after 3+ retries (300ms+)
```
**How to Set**:
1. Open `BotWorldSessionMgr.cpp`
2. Navigate to line 768
3. Click left margin to set breakpoint (red dot)
4. Right-click breakpoint → **Conditions**
5. Select "Conditional Expression"
6. Enter: `retries >= 3`
7. Click **Close**
**When Breakpoint Hits**:
```
Breakpoint hit at BotWorldSessionMgr.cpp:768
Current values:
i = 9
futures.size() = 10
futureGuids[9] = Player-1-00000033
retries = 5
completed = false
```
#### Step 4: Inspect Thread Pool State
**Immediate Window** (Debug → Windows → Immediate):
```cpp
Performance::GetThreadPool().GetActiveThreadCount()
// Output: 8 (all workers busy)
Performance::GetThreadPool().GetQueuedTaskCount()
// Output: 92 (92 tasks waiting!)
Performance::GetThreadPool().GetLoadPercentage()
// Output: 98.5% (thread pool overloaded)
```
#### Step 5: Inspect All Threads
**Threads Window** (Debug → Windows → Threads):
```
ID Name Location Priority
==== ======================== ================================= ========
1234 Main Thread BotWorldSessionMgr::Update:768 Normal
5678 ThreadPool Worker #1 BotAI::UpdateAI:450 (blocked) Normal
9012 ThreadPool Worker #2 Map::Update:2341 (waiting mutex) Normal
...
```
**Right-click thread** → **Freeze** to prevent it from executing
**Right-click thread** → **Switch to Thread** to inspect its stack
#### Step 6: Examine Call Stack
**Call Stack Window** (Debug → Windows → Call Stack):
```
worldserver.exe!BotWorldSessionMgr::Update() Line 768
worldserver.exe!World::Update(unsigned int) Line 3421
worldserver.exe!WorldRunnable::run() Line 89
worldserver.exe!Trinity::Threading::ThreadStart() Line 45
kernel32.dll!BaseThreadInitThunk()
ntdll.dll!RtlUserThreadStart()
```
**Double-click any frame** to navigate to source
---
### Method 2: Automated Break-on-Timeout
**Create Debug Build with Break Macro**:
**Add to BotWorldSessionMgr.cpp** (line 766):
```cpp
if (!completed)
{
#ifdef _DEBUG
// Automatic debugger break on timeout (Debug builds only)
if (IsDebuggerPresent())
{
TC_LOG_FATAL("module.playerbot.session",
"🛑 BREAKING INTO DEBUGGER: Future {}/{} timeout",
i + 1, futures.size());
__debugbreak(); // Triggers breakpoint if debugger attached
}
#endif
TC_LOG_FATAL("module.playerbot.session",
"⚠️ POTENTIAL DEADLOCK: Future {}/{} (bot {}) timeout after {:.1f}s",
i + 1, futures.size(), futureGuids[i].ToString(),
static_cast<double>(MAX_RETRIES * 100) / 1000.0);
}
```
**Usage**:
1. Build Debug configuration
2. Attach Visual Studio debugger
3. Run worldserver.exe
4. On timeout, execution automatically breaks
5. Inspect state in debugger
---
### Method 3: Time Travel Debugging (Enterprise Feature)
**Requirements**: Visual Studio Enterprise 2022+
#### Enable IntelliTrace
1. **Tools → Options → IntelliTrace**
2. Select: **IntelliTrace events and call information**
3. **IntelliTrace events**: Enable all "Threading" events
4. **Advanced → Maximum disk space**: 10 GB+
5. Click **OK**
#### Record Session
1. **Debug → Start Diagnostic Tools Session**
2. Select: **CPU Usage + IntelliTrace**
3. Launch worldserver.exe
4. Let deadlock occur
5. **Stop Collection**
#### Analyze Historical Execution
1. **Debug → IntelliTrace → IntelliTrace Events**
2. Filter: "Threading"
3. Find: "Thread blocked" or "Mutex wait" events
4. **Right-click event** → **Activate Historical Debugging**
5. Navigate backwards through time to see what caused the block
---
## AUTOMATED DEBUGGING TOOLS
### Tool 1: Deadlock Detector with Auto-Dump
**Create**: `scripts/debug_deadlock.ps1`
```powershell
# Automated Deadlock Detection & Dump Creation
# Usage: .\debug_deadlock.ps1 -ProcessName "worldserver" -TimeoutSeconds 60
param(
[string]$ProcessName = "worldserver",
[int]$TimeoutSeconds = 60,
[string]$OutputDir = "C:\TrinityBots\deadlock_dumps"
)
Write-Host "🔍 Deadlock Monitor Started" -ForegroundColor Cyan
Write-Host " Process: $ProcessName" -ForegroundColor Gray
Write-Host " Timeout: $TimeoutSeconds seconds" -ForegroundColor Gray
Write-Host " Output: $OutputDir" -ForegroundColor Gray
# Ensure output directory exists
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
# Find process
$process = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue
if (-not $process) {
Write-Host "❌ Process '$ProcessName' not found!" -ForegroundColor Red
exit 1
}
Write-Host "✅ Found process PID: $($process.Id)" -ForegroundColor Green
# Monitor log file for deadlock warnings
$logFile = "M:\Wplayerbot\logs\Server.log"
$lastSize = (Get-Item $logFile).Length
Write-Host "📊 Monitoring log: $logFile" -ForegroundColor Cyan
while ($true) {
Start-Sleep -Seconds 1
$currentSize = (Get-Item $logFile).Length
if ($currentSize -gt $lastSize) {
# Read new content
$newContent = Get-Content $logFile -Tail 100 | Select-String "DEADLOCK DETECTED"
if ($newContent) {
Write-Host "🚨 DEADLOCK DETECTED! Creating dump..." -ForegroundColor Red
# Create timestamp
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$dumpFile = Join-Path $OutputDir "deadlock_$timestamp.dmp"
# Create full memory dump using procdump
& "procdump.exe" -ma $process.Id $dumpFile
Write-Host "✅ Dump created: $dumpFile" -ForegroundColor Green
# Extract recent log entries
$logExtract = Join-Path $OutputDir "deadlock_$timestamp.log"
Get-Content $logFile -Tail 1000 | Out-File $logExtract
Write-Host "📄 Log extract: $logExtract" -ForegroundColor Green
# Auto-open in Visual Studio
Write-Host "🔧 Opening dump in Visual Studio..." -ForegroundColor Cyan
& "devenv.exe" "/debugexe" $dumpFile
break
}
$lastSize = $currentSize
}
}
```
**Installation**:
1. Download **ProcDump** from Sysinternals: https://docs.microsoft.com/en-us/sysinternals/downloads/procdump
2. Add `procdump.exe` to PATH
3. Save script as `C:\TrinityBots\scripts\debug_deadlock.ps1`
**Usage**:
```powershell
cd C:\TrinityBots\scripts
.\debug_deadlock.ps1
```
### Tool 2: Thread Pool Analyzer
**Create**: `scripts/analyze_threadpool.ps1`
```powershell
# Real-time Thread Pool Analysis
param(
[string]$LogFile = "M:\Wplayerbot\logs\Server.log"
)
Write-Host "📊 Thread Pool Analyzer" -ForegroundColor Cyan
# Extract thread pool metrics from logs
$metrics = Get-Content $LogFile | Select-String "ThreadPool:" | ForEach-Object {
if ($_ -match "ThreadPool: (\d+) active/(\d+) total, (\d+) queued") {
[PSCustomObject]@{
Active = [int]$Matches[1]
Total = [int]$Matches[2]
Queued = [int]$Matches[3]
Load = [math]::Round(([int]$Matches[1] / [int]$Matches[2]) * 100, 1)
}
}
}
# Calculate statistics
$avgActive = ($metrics | Measure-Object -Property Active -Average).Average
$avgQueued = ($metrics | Measure-Object -Property Queued -Average).Average
$maxQueued = ($metrics | Measure-Object -Property Queued -Maximum).Maximum
$avgLoad = ($metrics | Measure-Object -Property Load -Average).Average
Write-Host "`n📈 Thread Pool Statistics:" -ForegroundColor Green
Write-Host " Average Active Threads: $([math]::Round($avgActive, 1))" -ForegroundColor Gray
Write-Host " Average Queued Tasks: $([math]::Round($avgQueued, 1))" -ForegroundColor Gray
Write-Host " Maximum Queued Tasks: $maxQueued" -ForegroundColor Gray
Write-Host " Average Load: $([math]::Round($avgLoad, 1))%" -ForegroundColor Gray
if ($avgLoad -gt 80) {
Write-Host "`n⚠️ WARNING: Thread pool overloaded! Consider increasing worker count." -ForegroundColor Yellow
}
if ($maxQueued -gt 50) {
Write-Host "⚠️ WARNING: High task queue depth! Increase timeout or workers." -ForegroundColor Yellow
}
```
---
## CONFIGURATION & USAGE
### Step 1: Apply Enhanced Logging
**Edit**: `src/modules/Playerbot/Session/BotWorldSessionMgr.cpp`
Apply all logging enhancements from [Enhanced Logging System](#enhanced-logging-system) section.
### Step 2: Configure Adaptive Timeout
**Add to playerbots.conf**:
```ini
###################################################################################################
# DEADLOCK DETECTION SETTINGS
###################################################################################################
# Future timeout configuration
# Default: 500ms (5 retries × 100ms)
# Increase if experiencing false deadlock warnings under high load
#
# BotSession.FutureTimeoutMs
# Description: Maximum time to wait for bot update future completion
# Default: 500
# Range: 100-5000 (0.1s to 5s)
#
BotSession.FutureTimeoutMs = 2000
# BotSession.FutureRetryIntervalMs
# Description: Interval between future status checks
# Default: 100
# Range: 50-500
#
BotSession.FutureRetryIntervalMs = 100
# BotSession.EnableDeadlockDetection
# Description: Enable comprehensive deadlock detection with call stacks
# Default: 1 (enabled)
# Values: 0 (disabled), 1 (enabled)
#
BotSession.EnableDeadlockDetection = 1
# BotSession.AutoBreakOnDeadlock
# Description: Automatically break into debugger on deadlock (Debug builds only)
# Default: 0 (disabled)
# Values: 0 (disabled), 1 (enabled)
#
BotSession.AutoBreakOnDeadlock = 1
```
### Step 3: Build with Enhanced Diagnostics
**CMake Configuration**:
```bash
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DWITH_WARNINGS=1 \
-DWITH_COREDEBUG=1 \
-DUSE_COREPCH=0 \
../
cmake --build . --config RelWithDebInfo --target worldserver -j8
```
### Step 4: Run with Monitoring
**Terminal 1** - Server:
```bash
cd C:\TrinityBots\TrinityCore\build\bin\RelWithDebInfo
.\worldserver.exe
```
**Terminal 2** - Deadlock Monitor:
```powershell
cd C:\TrinityBots\scripts
.\debug_deadlock.ps1
```
**Terminal 3** - Log Tail:
```bash
tail -f M:\Wplayerbot\logs\Server.log | grep -E "(DEADLOCK|ThreadPool)"
```
---
## ADVANCED TECHNIQUES
### Technique 1: Conditional Breakpoint with Logging
**Visual Studio Breakpoint**:
- Location: `BotWorldSessionMgr.cpp:768`
- Condition: `retries >= 4`
- Action: **When Hit** → Log message to Output window
- Message: `Future {i+1}/{futures.size()} timeout for bot {futureGuids[i].ToString()}, retries={retries}`
- ☑ Continue execution
**Result**: Non-intrusive logging of near-timeouts without stopping execution
### Technique 2: Data Breakpoint on Future State
1. Break at line 732 (future.wait_for)
2. **Debug → New Breakpoint → Data Breakpoint**
3. Address: `&future` (address of std::future object)
4. Byte count: `sizeof(future)`
5. Break when: **Value changes**
**Result**: Break whenever future state transitions (useful for race conditions)
### Technique 3: Parallel Stacks View
**During Deadlock**:
1. **Debug → Windows → Parallel Stacks**
2. View all threads simultaneously
3. Identify threads waiting on same mutex
4. Trace back to common synchronization point
### Technique 4: Concurrency Visualizer
**Visual Studio Enterprise Only**:
1. **Analyze → Concurrency Visualizer → Start with Current Project**
2. Run scenario until deadlock
3. **Stop Collection**
4. View timeline showing:
- Thread execution (green bars)
- Thread blocking (red bars)
- Synchronization events
- Context switches
---
## QUICK REFERENCE CHECKLIST
### When You See "DEADLOCK DETECTED"
- [ ] **Step 1**: Check if timeout is appropriate (2000ms+ recommended for high load)
- [ ] **Step 2**: Verify thread pool isn't overloaded (check log for "ThreadPool: X active/Y total, Z queued")
- [ ] **Step 3**: If Z (queued) > 50, increase thread pool size or bot update interval
- [ ] **Step 4**: If issue persists, attach Visual Studio debugger
- [ ] **Step 5**: Set breakpoint at `BotWorldSessionMgr.cpp:768` with condition `retries >= 4`
- [ ] **Step 6**: When breakpoint hits, inspect Call Stack and Threads windows
- [ ] **Step 7**: Check for mutex deadlocks using Parallel Stacks view
- [ ] **Step 8**: Create memory dump with ProcDump for offline analysis
- [ ] **Step 9**: Use Concurrency Visualizer to identify blocking patterns
- [ ] **Step 10**: Document findings and adjust timeout/thread pool configuration
---
## SUMMARY
**Key Improvements**:
1. ✅ Fixed integer truncation in timeout logging (0s → 0.5s display)
2. ✅ Added comprehensive diagnostic logging at each stage
3. ✅ Provided Visual Studio Enterprise debugging workflows
4. ✅ Created automated monitoring and dump creation tools
5. ✅ Configured adaptive timeouts via playerbots.conf
6. ✅ Documented advanced debugging techniques
**Expected Outcome**:
- Accurate timeout reporting in logs
- Rich diagnostic context for troubleshooting
- Automated dump creation on real deadlocks
- Visual Studio integration for deep analysis
- Reduced false positive warnings via configurable timeouts
**Next Steps**:
1. Apply logging enhancements to BotWorldSessionMgr.cpp
2. Configure timeouts in playerbots.conf (start with 2000ms)
3. Rebuild with RelWithDebInfo
4. Run with automated monitoring
5. Analyze any remaining timeout warnings with Visual Studio