Files
ThordekkCore/PANDORAS_BOX_ANALYSIS.md
T

298 lines
7.5 KiB
Markdown
Raw Normal View History

2026-01-20 21:33:16 -03:00
# PANDORA'S BOX - OPTION 5 THREAD SAFETY ANALYSIS
**Generated**: October 27, 2025 23:45
**Crash**: `Spell.cpp:603` - `m_spellModTakingSpell` assertion
**Root Cause**: Fire-and-forget removed synchronization
---
## THE PROBLEM YOU IDENTIFIED
You're absolutely correct - Option 5 opened Pandora's Box!
### What Option 5 Changed
**BEFORE (OLD Synchronization Barrier):**
```cpp
// Submit bot updates to thread pool
for (bot in bots) {
futures.push_back(ThreadPool::Submit(updateLogic));
}
// WAIT FOR ALL BOT UPDATES TO COMPLETE
for (future in futures) {
future.wait_for(timeout); // BLOCKS main thread
}
// ONLY AFTER ALL BOTS FINISH:
// Main thread continues to Map::SendObjectUpdates()
```
**AFTER (Option 5 Fire-and-Forget):**
```cpp
// Submit bot updates to thread pool
for (bot in bots) {
ThreadPool::Submit(updateLogic); // NO WAIT
}
// IMMEDIATELY CONTINUE (no waiting!)
// Main thread proceeds to Map::SendObjectUpdates()
// WHILE bot updates still running in background!
```
### The Race Condition
```
Timeline with OLD code:
0ms: Main thread submits 100 bot updates
100ms: Main thread WAITS at synchronization barrier
200ms: Worker threads update bots (Player::Update)
300ms: All bot updates complete
400ms: Main thread wakes up
500ms: Main thread runs Map::SendObjectUpdates
```
```
Timeline with NEW code (Option 5):
0ms: Main thread submits 100 bot updates
1ms: Main thread IMMEDIATELY continues (no wait!)
2ms: Main thread runs Map::SendObjectUpdates
... Worker threads STILL updating bots in parallel!
RACE CONDITION:
- Map worker thread: Player::Update → Spell::~Spell
- Bot update thread: session->Update() → Player modification
- CRASH: Both accessing Player state simultaneously!
```
---
## THE ASSERTION FAILURE
**Spell.cpp:603**:
```cpp
Spell::~Spell()
{
ASSERT(m_caster->ToPlayer()->m_spellModTakingSpell != this);
// ↑ This fails because another thread is modifying the Player!
}
```
**What Happened:**
1. Map worker thread starts destroying LOGINEFFECT spell
2. Spell destructor checks `m_spellModTakingSpell`
3. **AT THE SAME TIME**: Bot session update thread modifies Player spell state
4. Assertion fails - data race!
---
## WHY THIS DIDN'T HAPPEN BEFORE
### With Synchronization Barrier (OLD)
The main thread BLOCKED until all bot sessions finished updating:
- Bot sessions run in thread pool
- **BUT**: Main thread waits for them to complete
- Map updates happen AFTER bot updates finish
- **No overlap = No race condition**
### With Fire-and-Forget (Option 5)
The main thread does NOT wait:
- Bot sessions run in thread pool
- Main thread continues immediately
- Map updates happen WHILE bot updates still running
- **Overlap = Race condition!**
---
## THE REAL PROBLEM
TrinityCore's architecture assumes **single-threaded access to Player objects**:
1. **Map system** updates players on map worker threads
2. **Session system** was designed for **sequential** updates
3. **Player objects** are NOT thread-safe for concurrent access
**Our bot sessions** are now updating Player objects in parallel with map updates!
---
## WHY THE SYNCHRONIZATION BARRIER EXISTED
The OLD code's synchronization barrier was there for a **CRITICAL REASON**:
**Ensure bot session updates complete BEFORE main thread continues**
Without this barrier:
- Bot updates run in background
- Main thread proceeds immediately
- Map updates start while bots still updating
- **Multiple threads access same Player → CRASH**
---
## OPTIONS TO FIX
### Option A: Restore Synchronization (Regression)
Go back to waiting for futures - but this brings back the deadlock!
### Option B: Single-Threaded Bot Updates
Don't use thread pool at all - update bots sequentially on main thread
### Option C: Per-Player Locking
Add mutex to each Player object - but massive performance hit
### Option D: Message Passing Architecture
Bot updates send "messages" to apply changes, main thread applies them
### Option E: Separate Update Phase
Update bots in DIFFERENT phase than map updates (before or after)
---
## RECOMMENDED FIX: Option E (Separate Phase)
**Concept**: Update bot sessions in a phase where map updates are NOT running
```cpp
void World::Update(uint32 diff)
{
// Phase 1: Update bot sessions (thread pool, parallel)
sBotWorldSessionMgr->UpdateSessions(diff);
// Phase 2: WAIT for bot sessions to complete
sBotWorldSessionMgr->WaitForCompletion();
// Phase 3: Update maps (now safe - bots done)
sMapMgr->Update(diff);
}
```
**Benefits:**
- Bot sessions still run in parallel (performance)
- But isolated from map updates (thread safety)
- No ObjectAccessor deadlock (different phase)
- Fire-and-forget within bot update phase only
---
## IMPLEMENTATION PLAN
### Step 1: Add Completion Tracking
```cpp
// BotWorldSessionMgr.h
class BotWorldSessionMgr {
private:
std::atomic<uint32> _pendingUpdates{0};
std::condition_variable _completionCV;
std::mutex _completionMutex;
public:
void WaitForCompletion();
};
```
### Step 2: Track Submitted Tasks
```cpp
// BotWorldSessionMgr.cpp UpdateSessions()
_pendingUpdates.store(sessionsToUpdate.size());
for (auto session : sessionsToUpdate) {
auto updateLogic = [this, ...]() {
// ... bot update ...
// Decrement pending counter
uint32 remaining = _pendingUpdates.fetch_sub(1) - 1;
if (remaining == 0) {
// Last bot finished - notify waiting thread
std::lock_guard lock(_completionMutex);
_completionCV.notify_one();
}
};
ThreadPool::Submit(updateLogic); // Fire-and-forget
}
```
### Step 3: Wait for Completion
```cpp
void BotWorldSessionMgr::WaitForCompletion()
{
std::unique_lock lock(_completionMutex);
_completionCV.wait(lock, [this] {
return _pendingUpdates.load() == 0;
});
}
```
### Step 4: Integrate into World::Update
```cpp
// World.cpp
void World::Update(uint32 diff)
{
// ... other systems ...
// PHASE 1: Start bot updates (parallel, non-blocking)
sBotWorldSessionMgr->UpdateSessions(diff);
// PHASE 2: Wait for bot updates to complete
sBotWorldSessionMgr->WaitForCompletion();
// PHASE 3: Now safe to update maps
sMapMgr->Update(diff);
// ... rest of update ...
}
```
---
## COMPARISON
| Approach | Parallel | Safe | Deadlock Risk |
|----------|----------|------|---------------|
| OLD (Sync Barrier) | ✅ Yes | ❌ **No** | ✅ **YES** (deadlock) |
| Option 5 (Fire-forget) | ✅ Yes | ❌ **No** | ✅ No (but crashes!) |
| **Option E (Phased)** | ✅ Yes | ✅ **Yes** | ✅ No |
---
## WHY OPTION E FIXES BOTH ISSUES
### Problem 1: Main Thread Deadlock (OLD)
**Cause**: Main thread waits for bots → bots wait for ObjectAccessor → ObjectAccessor waits for main thread
**Fix**: Bots update in SEPARATE PHASE before maps
- Bots don't compete with Map::SendObjectUpdates for locks
- No circular dependency
### Problem 2: Race Condition (Option 5)
**Cause**: Bot updates and map updates run simultaneously
**Fix**: Bots complete BEFORE maps start
- No simultaneous access to Player objects
- Thread-safe by design
---
## CONCLUSION
You were absolutely right - **Option 5 opened Pandora's Box!**
**The synchronization barrier was protecting us from race conditions**, even though it also caused deadlocks.
**The fix**: Keep the fire-and-forget pattern BUT wait for completion **before map updates start**, not during session updates.
This gives us:
- ✅ Parallel bot updates (performance)
- ✅ No deadlock (separate phase)
- ✅ Thread safety (sequential phases)
---
**NEXT ACTION**: Implement Option E (Phased Updates) to close Pandora's Box properly.