perf(quest): Fix mutex contention in QuestCompletion with shared_mutex

PROBLEM: QuestCompletion used std::mutex for read-heavy data structures
which blocked ALL readers when ANY read was happening. With 100+ bots
calling quest-related functions from ThreadPool workers, this caused
severe lock contention and task delays.

AFFECTED MUTEXES (all read-heavy patterns):
- _pausedBotsMutex: Checked on EVERY quest event (HandleQuestEvent)
- _questOrderMutex: Read frequently during quest prioritization
- _objectiveOrderMutex: Read during objective sequencing

SOLUTION:
1. Changed _pausedBotsMutex from std::mutex to std::shared_mutex
   - Read operations (find) use std::shared_lock (concurrent readers OK)
   - Write operations (insert/erase) use std::unique_lock (exclusive)

2. Changed _questOrderMutex from std::mutex to std::shared_mutex
   - All operations use std::unique_lock (mostly writes in current code)
   - Future optimization: use shared_lock for read-only accesses

3. Changed _objectiveOrderMutex from std::mutex to std::shared_mutex
   - All operations use std::unique_lock (writes only currently)

This eliminates a major ThreadPool bottleneck where every bot's
quest event processing was blocking on mutex acquisition.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: luis <[email protected]>
This commit is contained in:
agatho
2026-02-02 13:26:00 -03:00
committed by luis
co-authored by Claude Opus 4.5
parent 018de94dc3
commit 323ff2021f
2 changed files with 27 additions and 17 deletions
+18 -14
View File
@@ -2018,8 +2018,9 @@ void QuestCompletion::HandleQuestEvent(QuestEvent const& event)
return;
// Check if bot is paused (due to death)
// CRITICAL FIX: Use shared_lock for read-only check (allows concurrent readers)
{
std::lock_guard<std::mutex> lock(_pausedBotsMutex);
std::shared_lock<std::shared_mutex> lock(_pausedBotsMutex);
if (_pausedBots.find(_bot->GetGUID().GetCounter()) != _pausedBots.end())
{
TC_LOG_TRACE("playerbot.quest", "QuestCompletion: Bot {} paused, skipping event {}",
@@ -2428,7 +2429,8 @@ std::optional<CachedQuestPOI> QuestCompletion::GetCachedQuestPOI(ObjectGuid botG
*/
void QuestCompletion::PauseQuestCompletion(uint32 botGuid)
{
std::lock_guard<std::mutex> lock(_pausedBotsMutex);
// WRITE operation - requires exclusive lock
std::unique_lock<std::shared_mutex> lock(_pausedBotsMutex);
_pausedBots.insert(botGuid);
TC_LOG_DEBUG("playerbot.quest", "PauseQuestCompletion: Bot {} paused", botGuid);
@@ -2441,7 +2443,8 @@ void QuestCompletion::PauseQuestCompletion(uint32 botGuid)
*/
void QuestCompletion::ResumeQuestCompletion(uint32 botGuid)
{
std::lock_guard<std::mutex> lock(_pausedBotsMutex);
// WRITE operation - requires exclusive lock
std::unique_lock<std::shared_mutex> lock(_pausedBotsMutex);
_pausedBots.erase(botGuid);
TC_LOG_DEBUG("playerbot.quest", "ResumeQuestCompletion: Bot {} resumed", botGuid);
@@ -2824,7 +2827,7 @@ void QuestCompletion::OptimizeQuestCompletionOrder(Player* player)
}
{
std::lock_guard<std::mutex> lock(_questOrderMutex);
std::unique_lock<std::shared_mutex> lock(_questOrderMutex);
_botQuestOrder[player->GetGUID().GetCounter()] = std::move(optimizedOrder);
}
@@ -2929,7 +2932,7 @@ void QuestCompletion::OptimizeObjectiveSequence(Player* player, uint32 questId)
// Store optimized sequence
{
std::lock_guard<std::mutex> lock(_objectiveOrderMutex);
std::unique_lock<std::shared_mutex> lock(_objectiveOrderMutex);
_botObjectiveOrder[player->GetGUID().GetCounter()][questId] = std::move(optimizedOrder);
}
@@ -4771,7 +4774,7 @@ void QuestCompletion::HandleDungeonQuests(Player* player, uint32 dungeonId)
// Update quest order to prioritize dungeon quests
{
std::lock_guard<std::mutex> lock(_questOrderMutex);
std::unique_lock<std::shared_mutex> lock(_questOrderMutex);
std::vector<uint32>& questOrder = _botQuestOrder[botGuid];
// Remove dungeon quests from current order
@@ -4857,7 +4860,7 @@ void QuestCompletion::HandlePvPQuests(Player* player, uint32 questId)
// Prioritize this quest
{
std::lock_guard<std::mutex> lock(_questOrderMutex);
std::unique_lock<std::shared_mutex> lock(_questOrderMutex);
std::vector<uint32>& questOrder = _botQuestOrder[botGuid];
// Remove from current position
@@ -4953,7 +4956,7 @@ void QuestCompletion::HandleSeasonalQuests(Player* player)
// Prioritize seasonal quests at the top of the quest order
{
std::lock_guard<std::mutex> lock(_questOrderMutex);
std::unique_lock<std::shared_mutex> lock(_questOrderMutex);
std::vector<uint32>& questOrder = _botQuestOrder[botGuid];
// Remove seasonal quests from current positions
@@ -5040,7 +5043,7 @@ void QuestCompletion::HandleDailyQuests(Player* player)
// Prioritize daily quests
{
std::lock_guard<std::mutex> lock(_questOrderMutex);
std::unique_lock<std::shared_mutex> lock(_questOrderMutex);
std::vector<uint32>& questOrder = _botQuestOrder[botGuid];
// Remove daily quests from current positions
@@ -5339,7 +5342,7 @@ void QuestCompletion::AbandonUncompletableQuest(Player* player, uint32 questId)
// Remove from quest order
{
std::lock_guard<std::mutex> lock(_questOrderMutex);
std::unique_lock<std::shared_mutex> lock(_questOrderMutex);
auto& order = _botQuestOrder[botGuid];
order.erase(std::remove(order.begin(), order.end(), questId), order.end());
}
@@ -5545,8 +5548,9 @@ void QuestCompletion::UpdateBotQuestCompletion(Player* player, uint32 diff)
uint32 botGuid = player->GetGUID().GetCounter();
// Check if bot is paused (e.g., dead)
// CRITICAL FIX: Use shared_lock for read-only check (allows concurrent readers)
{
std::lock_guard<std::mutex> lock(_pausedBotsMutex);
std::shared_lock<std::shared_mutex> lock(_pausedBotsMutex);
if (_pausedBots.find(botGuid) != _pausedBots.end())
return;
}
@@ -5614,7 +5618,7 @@ void QuestCompletion::UpdateBotQuestCompletion(Player* player, uint32 diff)
// Sort by priority (use quest order if set)
std::vector<uint32>* questOrder = nullptr;
{
std::lock_guard<std::mutex> lock(_questOrderMutex);
std::unique_lock<std::shared_mutex> lock(_questOrderMutex);
auto orderIt = _botQuestOrder.find(botGuid);
if (orderIt != _botQuestOrder.end())
questOrder = &orderIt->second;
@@ -5772,13 +5776,13 @@ void QuestCompletion::ValidateQuestStates()
// Also clean up related data structures
{
std::lock_guard<std::mutex> lock(_questOrderMutex);
std::unique_lock<std::shared_mutex> lock(_questOrderMutex);
auto& order = _botQuestOrder[botGuid];
order.erase(std::remove(order.begin(), order.end(), questId), order.end());
}
{
std::lock_guard<std::mutex> lock(_objectiveOrderMutex);
std::unique_lock<std::shared_mutex> lock(_objectiveOrderMutex);
_botObjectiveOrder[botGuid].erase(questId);
}
}
@@ -690,7 +690,10 @@ private:
std::unordered_map<uint32, std::unordered_map<uint32, CachedQuestPOI>> _questPOICache;
// Paused bots (due to death)
mutable std::mutex _pausedBotsMutex;
// CRITICAL FIX: Changed to shared_mutex for read-heavy access pattern
// _pausedBots is checked on EVERY quest event (read), but rarely modified (write)
// With 100+ bots, std::mutex caused severe contention
mutable std::shared_mutex _pausedBotsMutex;
std::unordered_set<uint32> _pausedBots;
// EventBus callback subscription ID (for unsubscription in destructor)
@@ -728,11 +731,14 @@ private:
std::unordered_map<uint32, std::unordered_map<uint32, GroupQuestCoordinationData>> _groupQuestCoordination;
// Quest order optimization
mutable std::mutex _questOrderMutex;
// CRITICAL FIX: Changed to shared_mutex - quest order is read frequently (every update cycle)
// but only written during optimization (rare). std::mutex caused ThreadPool delays.
mutable std::shared_mutex _questOrderMutex;
std::unordered_map<uint32, std::vector<uint32>> _botQuestOrder; // botGuidCounter -> ordered questIds
// Objective order optimization
mutable std::mutex _objectiveOrderMutex;
// CRITICAL FIX: Changed to shared_mutex - same read-heavy pattern as quest order
mutable std::shared_mutex _objectiveOrderMutex;
std::unordered_map<uint32, std::unordered_map<uint32, std::vector<uint32>>> _botObjectiveOrder; // bot -> quest -> ordered objectives
// Path optimization