131 lines
5.1 KiB
Markdown
131 lines
5.1 KiB
Markdown
# DEADLOCK ROOT CAUSE ANALYSIS - THE ACTUAL SOURCE
|
|||
|
|
|
||
|
|
## CRITICAL DISCOVERY: The Deadlock is NOT in BotAI.cpp
|
||
|
|
|
||
|
|
After 12 deadlock fixes focused on BotAI.cpp, the deadlock persists because **we've been looking in the wrong place**.
|
||
|
|
|
||
|
|
## THE ACTUAL DEADLOCK SOURCE
|
||
|
|
|
||
|
|
**Location**: `GroupInvitationHandler.cpp`
|
||
|
|
**Lines**: 499-507
|
||
|
|
**Method**: `SendAcceptPacket()`
|
||
|
|
|
||
|
|
### The Deadly Lock Acquisition Chain
|
||
|
|
|
||
|
|
Thread 1 (World Update Thread) acquires locks in this order:
|
||
|
|
1. BotSessionMgr::UpdateAllSessions() - holds _sessionsMutex
|
||
|
|
2. BotAI::UpdateAI() - acquires shared_lock on BotAI::_mutex (in UpdateStrategies)
|
||
|
|
3. GroupInvitationHandler::Update() - called while BotAI::_mutex held
|
||
|
|
4. SendAcceptPacket() - tries botAI->ActivateStrategy("follow")
|
||
|
|
5. BotAI::ActivateStrategy() - tries to acquire UNIQUE_LOCK on _mutex
|
||
|
|
6. **DEADLOCK**: Can't upgrade shared_lock to unique_lock on same thread
|
||
|
|
|
||
|
|
## DETAILED ANALYSIS
|
||
|
|
|
||
|
|
### The Call Stack At Deadlock:
|
||
|
|
```
|
||
|
|
BotAI::UpdateAI() [Holds: shared_lock on BotAI::_mutex]
|
||
|
|
└─ UpdateStrategies(diff) [Acquires shared_lock - OK]
|
||
|
|
└─ _groupInvitationHandler->Update(diff) [Line 157 - still holds shared_lock]
|
||
|
|
└─ ProcessNextInvitation()
|
||
|
|
└─ AcceptInvitationInternal()
|
||
|
|
└─ SendAcceptPacket()
|
||
|
|
└─ Line 499: botAI->GetStrategy("follow") [Tries shared_lock AGAIN]
|
||
|
|
└─ Line 505: botAI->ActivateStrategy("follow") [Tries UNIQUE_LOCK - DEADLOCK]
|
||
|
|
└─ BotAI::ActivateStrategy()
|
||
|
|
└─ std::unique_lock lock(_mutex) [BLOCKED: can't upgrade shared to unique]
|
||
|
|
```
|
||
|
|
|
||
|
|
### Why This Deadlocks:
|
||
|
|
|
||
|
|
1. **BotAI::UpdateStrategies()** acquires `std::shared_lock` on `BotAI::_mutex`
|
||
|
|
2. Still holding that lock, **BotAI::UpdateAI()** calls `GroupInvitationHandler::Update()`
|
||
|
|
3. GroupInvitationHandler processes group join, calls `SendAcceptPacket()`
|
||
|
|
4. SendAcceptPacket() tries to call **BotAI::ActivateStrategy()** (line 505)
|
||
|
|
5. ActivateStrategy() needs `std::unique_lock` on same `BotAI::_mutex`
|
||
|
|
6. **DEADLOCK**: Same thread cannot upgrade shared_lock to unique_lock
|
||
|
|
|
||
|
|
### Root Cause:
|
||
|
|
**Reentrant lock acquisition from callback context**
|
||
|
|
|
||
|
|
The GroupInvitationHandler is called FROM BotAI::UpdateAI() while locks are held, then tries to call BACK into BotAI methods that need stronger locks.
|
||
|
|
|
||
|
|
## THE SOLUTION
|
||
|
|
|
||
|
|
### IMMEDIATE FIX (Remove Redundant Code):
|
||
|
|
|
||
|
|
**File**: `c:/TrinityBots/TrinityCore/src/modules/Playerbot/Group/GroupInvitationHandler.cpp`
|
||
|
|
**Action**: DELETE lines 497-507
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
// DELETE THESE LINES (they cause the deadlock):
|
||
|
|
// BACKUP FIX: Directly activate follow strategy as fallback
|
||
|
|
TC_LOG_INFO("module.playerbot.group", "Bot directly activating follow strategy", _bot->GetName());
|
||
|
|
if (botAI->GetStrategy("follow")) // ← Line 499: REENTRANT LOCK
|
||
|
|
{
|
||
|
|
TC_LOG_INFO("module.playerbot.group", "Bot already has follow strategy", _bot->GetName());
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
botAI->ActivateStrategy("follow"); // ← Line 505: DEADLOCK SOURCE
|
||
|
|
TC_LOG_INFO("module.playerbot.group", "Bot activated follow strategy", _bot->GetName());
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Reason**: OnGroupJoined() is already called at lines 455 and 494, which properly activates the follow strategy. Lines 497-507 are redundant backup code that creates the deadlock.
|
||
|
|
|
||
|
|
### WHY PREVIOUS FIXES DIDN'T WORK
|
||
|
|
|
||
|
|
All 12 fixes focused on **BotAI::UpdateStrategies()** lock ordering, but that's not the problem:
|
||
|
|
- UpdateStrategies() correctly acquires and releases locks
|
||
|
|
- The issue is in **GroupInvitationHandler::SendAcceptPacket()** calling back into BotAI
|
||
|
|
- The callback happens WHILE BotAI::_mutex is still held (shared)
|
||
|
|
- Then tries to acquire unique_lock on the SAME mutex = deadlock
|
||
|
|
|
||
|
|
## VALIDATION
|
||
|
|
|
||
|
|
After fix, test:
|
||
|
|
1. Create bot
|
||
|
|
2. Invite bot to group
|
||
|
|
3. Bot auto-accepts
|
||
|
|
4. Bot starts following
|
||
|
|
5. **NO "resource deadlock would occur" exception**
|
||
|
|
|
||
|
|
## THREADING MODEL
|
||
|
|
|
||
|
|
### Single Main Thread:
|
||
|
|
```
|
||
|
|
Main World Thread:
|
||
|
|
└─> PlayerbotModule::OnUpdate(diff)
|
||
|
|
└─> BotSessionMgr::UpdateAllSessions(diff)
|
||
|
|
└─> For each session (SEQUENTIAL):
|
||
|
|
└─> BotSession::Update(diff)
|
||
|
|
└─> BotAI::UpdateAI(diff)
|
||
|
|
├─ UpdateStrategies() [shared_lock]
|
||
|
|
└─ GroupInvitationHandler::Update() [CALLBACK - DEADLOCK HERE]
|
||
|
|
```
|
||
|
|
|
||
|
|
**Key**: Single-threaded, but deadlock happens from reentrant callback, not threading.
|
||
|
|
|
||
|
|
## COMPREHENSIVE MUTEX INVENTORY
|
||
|
|
|
||
|
|
**Total**: 200+ mutexes across 150+ classes in Playerbot module
|
||
|
|
|
||
|
|
**High-Risk** (Direct BotAI interaction):
|
||
|
|
- GroupInvitationHandler::_invitationMutex
|
||
|
|
- BotSession::_packetMutex
|
||
|
|
- BotSessionMgr::_sessionsMutex
|
||
|
|
- ActionPriority::_queueMutex, _poolMutex
|
||
|
|
- CooldownManager::_cooldownMutex, _categoryMutex
|
||
|
|
|
||
|
|
**Medium-Risk** (Indirect):
|
||
|
|
- All ClassAI specializations (200+ _cooldownMutex)
|
||
|
|
- Combat managers
|
||
|
|
- Performance monitors
|
||
|
|
|
||
|
|
## CONCLUSION
|
||
|
|
|
||
|
|
The deadlock source was **GroupInvitationHandler.cpp lines 497-507**, not BotAI.cpp.
|
||
|
|
|
||
|
|
**Immediate action**: Delete the redundant backup code that tries to activate strategy from callback context.
|