164 lines
5.6 KiB
Markdown
164 lines
5.6 KiB
Markdown
# TrinityCore Singleton Deadlock Investigation Results
|
||
|
||
## EXECUTIVE SUMMARY
|
||
|
||
### Verdict: **PARTIALLY AGREE** with User's Hypothesis
|
||
|
||
The investigation reveals a **CRITICAL DEADLOCK PATTERN** caused by the interaction between:
|
||
1. **260+ files still using mutexes** in the Playerbot module (despite Phase 2 removal claims)
|
||
2. **TrinityCore singleton locks** (MapManager::_mapsLock, ObjectAccessor locks, etc.)
|
||
3. **Recursive mutex usage** creating lock ordering inversions
|
||
|
||
## THE SMOKING GUN: Deadlock Cycle Identified
|
||
|
||
### Lock Ordering Pattern Causing Deadlock:
|
||
|
||
```
|
||
Thread 1 (World Update):
|
||
1. MapManager::DoDelayedMovesAndRemoves() → acquires MapManager::_mapsLock (shared)
|
||
2. Map::Update() → processes all players/creatures
|
||
3. Player::Update() → for bot players
|
||
4. BotAI::UpdateAI() → acquires BotAI::_mutex (recursive)
|
||
5. BotAI calls GetMap() → TRIES to acquire MapManager::_mapsLock → DEADLOCK!
|
||
|
||
Thread 2 (Bot Session Update):
|
||
1. BotWorldSessionMgr::Update() → acquires _sessionsMutex (recursive)
|
||
2. Processes bot session updates
|
||
3. Bot needs map info → calls sMapMgr->FindMap() → acquires MapManager::_mapsLock
|
||
4. While holding map lock, tries to update bot → TRIES BotAI::_mutex → DEADLOCK!
|
||
```
|
||
|
||
## CRITICAL FINDINGS
|
||
|
||
### 1. **Remaining Mutex Count (SHOCKING!)**
|
||
- **260 files** still contain mutex/lock constructs
|
||
- **50 files** use TrinityCore singletons directly
|
||
- Key offenders with recursive mutexes:
|
||
- `BotAI::_mutex` (recursive_mutex)
|
||
- `BotAccountMgr` (3 recursive_mutexes!)
|
||
- `BotWorldSessionMgr::_sessionsMutex` (recursive)
|
||
|
||
### 2. **Singleton Lock Patterns**
|
||
|
||
#### MapManager (Most Critical)
|
||
```cpp
|
||
// MapManager.h
|
||
mutable std::shared_mutex _mapsLock; // Line 150
|
||
|
||
// Every FindMap(), CreateMap() acquires this lock
|
||
// Called from 20+ bot files during normal operation
|
||
```
|
||
|
||
#### Map Access Pattern Issues
|
||
- `GetMap()` called from combat, movement, death recovery
|
||
- `FindMap()` called during bot spawning/teleportation
|
||
- Map locks held while updating ALL entities on map
|
||
- 100+ bots × map checks = massive contention
|
||
|
||
### 3. **Recursive Mutex Anti-Pattern**
|
||
|
||
The codebase uses `std::recursive_mutex` extensively with this justification:
|
||
```cpp
|
||
// BotAI.h line 652
|
||
// DEADLOCK FIX #14: Changed from std::shared_mutex to std::recursive_mutex
|
||
// Solution: Use std::recursive_mutex which ALLOWS the same thread to acquire
|
||
```
|
||
|
||
**THIS IS NOT A FIX - IT'S HIDING THE PROBLEM!**
|
||
|
||
Recursive mutexes mask lock ordering issues and create:
|
||
- Hidden reentrancy bugs
|
||
- Unpredictable lock hold times
|
||
- Impossible-to-debug nested locking
|
||
|
||
## ROOT CAUSE ANALYSIS
|
||
|
||
### The Perfect Storm:
|
||
1. **Singleton Convoy Effect**: All 100+ bots hitting MapManager::_mapsLock
|
||
2. **Lock Inversion**: Bot locks held while calling singletons, singleton locks held while updating bots
|
||
3. **Recursive Mutex Masking**: Can't detect ordering issues until production
|
||
4. **10-Second Stalls**: Lock timeout in TrinityCore is ~10 seconds
|
||
|
||
## RECOMMENDATIONS
|
||
|
||
### IMMEDIATE ACTION (Stop the Bleeding)
|
||
|
||
1. **REMOVE ALL REMAINING MUTEXES** from Playerbot module:
|
||
- 260 files need immediate attention
|
||
- Focus on BotAI, BotWorldSessionMgr, BotAccountMgr first
|
||
|
||
2. **ELIMINATE SINGLETON CALLS FROM LOCKED SECTIONS**:
|
||
- Never call GetMap(), FindMap() while holding bot locks
|
||
- Cache map pointers, don't fetch repeatedly
|
||
- Use weak_ptr for map references
|
||
|
||
### LONG-TERM SOLUTION (Complete Rewrite)
|
||
|
||
3. **LOCK-FREE ARCHITECTURE**:
|
||
```cpp
|
||
// BEFORE (Current - DEADLOCK PRONE)
|
||
class BotAI {
|
||
mutable std::recursive_mutex _mutex;
|
||
void UpdateAI() {
|
||
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
||
Map* map = _bot->GetMap(); // DEADLOCK!
|
||
}
|
||
};
|
||
|
||
// AFTER (Lock-Free Message Passing)
|
||
class BotAI {
|
||
// NO MUTEX!
|
||
concurrent_queue<BotCommand> _commands;
|
||
void UpdateAI() {
|
||
BotCommand cmd;
|
||
while (_commands.try_pop(cmd)) {
|
||
ProcessCommand(cmd); // No locks needed
|
||
}
|
||
}
|
||
};
|
||
```
|
||
|
||
4. **SINGLETON ABSTRACTION LAYER**:
|
||
- Create BotMapCache to avoid repeated singleton calls
|
||
- Use read-only snapshots updated once per world tick
|
||
- Batch singleton operations
|
||
|
||
## VALIDATION EVIDENCE
|
||
|
||
### Proof of Deadlock Pattern:
|
||
1. **BotAI.cpp**: Uses recursive mutex (line 668), calls GetMap()
|
||
2. **MapManager**: Uses shared_mutex, called from bot update
|
||
3. **BotWorldSessionMgr**: Holds lock while processing updates (line 392)
|
||
4. **Circular dependency**: Bot→Map→Bot creates cycle
|
||
|
||
### Performance Impact:
|
||
- Each singleton call: ~100-500ns overhead
|
||
- 100 bots × 30 updates/sec × 10 singleton calls = 30,000 lock acquisitions/sec
|
||
- Lock contention at this scale = guaranteed deadlocks
|
||
|
||
## FINAL ANSWER
|
||
|
||
### Do we need to eliminate ALL singleton calls?
|
||
|
||
**YES AND NO:**
|
||
|
||
**YES** - Eliminate singleton calls from ANY locked section
|
||
**NO** - Singleton calls are safe IF:
|
||
1. No bot locks are held when calling
|
||
2. Results are cached appropriately
|
||
3. Called from lock-free code paths
|
||
|
||
**BUT** given the current architecture with 260+ files using locks, the safest approach is:
|
||
|
||
## **ELIMINATE ALL SINGLETON CALLS UNTIL LOCK-FREE ARCHITECTURE IS COMPLETE**
|
||
|
||
The user is fundamentally correct - the current mutex-heavy architecture cannot safely interact with TrinityCore singletons without deadlocking.
|
||
|
||
## Action Items
|
||
|
||
1. **IMMEDIATE**: Remove BotAI::_mutex - this is the primary deadlock source
|
||
2. **URGENT**: Eliminate all recursive_mutex usage (anti-pattern)
|
||
3. **CRITICAL**: Implement lock-free message passing for all bot operations
|
||
4. **IMPORTANT**: Cache all singleton data, never fetch under locks
|
||
|
||
The 10-second stalls will continue until these mutexes are removed. |