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:
committed by
luis
co-authored by
Claude Opus 4.5
parent
018de94dc3
commit
323ff2021f
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user