Add thread safety for group operations (#1816)

Fixes crashes and race conditions when bots perform group/guild/arena
operations by moving thread-unsafe code to world thread.

Potentially fixes #1124

## Changes

- Added operation queue system that runs in world thread
- Group operations (invite, remove, convert to raid, set leader) now
queued
- Arena formation refactored to use queue
- Guild operations changed to use packet queueing

## Testing

Set `MapUpdate.Threads` > 1 in worldserver.conf to enable multiple map
threads, then test:
- Group formation and disbanding
- Arena team formation
- Guild operations (invite, promote, demote, remove)

- Run with TSAN
cmake ../ \
  -DCMAKE_CXX_FLAGS="-fsanitize=thread -g -O1" \
  -DCMAKE_C_FLAGS="-fsanitize=thread -g -O1" \
  -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread" \
  -DCMAKE_INSTALL_PREFIX=/path/to/install \
  -DCMAKE_BUILD_TYPE=RelWithDebInfo

build

export
TSAN_OPTIONS="log_path=tsan_report:halt_on_error=0:second_deadlock_stack=1"
./worldserver

The crashes/race conditions should no longer occur with concurrent map
threads.

## New Files

- `PlayerbotOperation.h` - Base class defining the operation interface
(Execute, IsValid, GetPriority)
- `PlayerbotOperations.h` - Concrete implementations:
GroupInviteOperation, GroupRemoveMemberOperation,
GroupConvertToRaidOperation, GroupSetLeaderOperation,
ArenaGroupFormationOperation
- `PlayerbotWorldThreadProcessor.h/cpp` - Singleton processor with
mutex-protected queue, processes operations in WorldScript::OnUpdate
hook, handles batch processing and validation

---------

Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
Co-authored-by: SaW <swerkhoven@outlook.com>
Co-authored-by: bash <hermensb@gmail.com>
This commit is contained in:
blinkysc
2025-11-21 14:55:55 -06:00
committed by GitHub
parent d97870facd
commit 10213d8381
11 changed files with 1015 additions and 55 deletions

View File

@@ -9,7 +9,9 @@
#include "Event.h"
#include "GuildMgr.h"
#include "Log.h"
#include "PlayerbotOperations.h"
#include "Playerbots.h"
#include "PlayerbotWorldThreadProcessor.h"
#include "ServerFacade.h"
bool InviteToGroupAction::Invite(Player* inviter, Player* player)
@@ -27,7 +29,10 @@ bool InviteToGroupAction::Invite(Player* inviter, Player* player)
{
if (GET_PLAYERBOT_AI(player) && !GET_PLAYERBOT_AI(player)->IsRealPlayer())
if (!group->isRaidGroup() && group->GetMembersCount() > 4)
group->ConvertToRaid();
{
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(inviter->GetGUID());
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
}
}
WorldPacket p;
@@ -89,7 +94,10 @@ bool InviteNearbyToGroupAction::Execute(Event event)
// When inviting the 5th member of the group convert to raid for future invites.
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
bot->GetGroup()->GetMembersCount() > 3)
group->ConvertToRaid();
{
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
}
if (sPlayerbotAIConfig->inviteChat && sRandomPlayerbotMgr->IsRandomBot(bot))
{
@@ -221,7 +229,8 @@ bool InviteGuildToGroupAction::Execute(Event event)
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
bot->GetGroup()->GetMembersCount() > 3)
{
group->ConvertToRaid();
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
}
if (sPlayerbotAIConfig->inviteChat &&
@@ -362,7 +371,10 @@ bool LfgAction::Execute(Event event)
if (param.empty() || param == "5" || group->isRaidGroup())
return false; // Group or raid is full so stop trying.
else
group->ConvertToRaid(); // We want a raid but are in a group so convert and continue.
{
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(requester->GetGUID());
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
}
}
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();