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

@@ -58,6 +58,14 @@ Player* GuidManageAction::GetPlayer(Event event)
return nullptr;
}
void GuidManageAction::SendPacket(WorldPacket const& packet)
{
// make a heap copy because QueuePacket takes ownership
WorldPacket* data = new WorldPacket(packet);
bot->GetSession()->QueuePacket(data);
}
bool GuidManageAction::Execute(Event event)
{
Player* player = GetPlayer(event);
@@ -84,12 +92,6 @@ bool GuildInviteAction::isUseful()
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_INVITE);
}
void GuildInviteAction::SendPacket(WorldPacket packet)
{
WorldPackets::Guild::GuildInviteByName data = WorldPacket(packet);
bot->GetSession()->HandleGuildInviteOpcode(data);
}
bool GuildInviteAction::PlayerIsValid(Player* member)
{
return !member->GetGuildId() && (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) ||
@@ -101,12 +103,6 @@ bool GuildPromoteAction::isUseful()
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_PROMOTE);
}
void GuildPromoteAction::SendPacket(WorldPacket packet)
{
WorldPackets::Guild::GuildPromoteMember data = WorldPacket(packet);
bot->GetSession()->HandleGuildPromoteOpcode(data);
}
bool GuildPromoteAction::PlayerIsValid(Player* member)
{
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member) - 1;
@@ -117,12 +113,6 @@ bool GuildDemoteAction::isUseful()
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_DEMOTE);
}
void GuildDemoteAction::SendPacket(WorldPacket packet)
{
WorldPackets::Guild::GuildDemoteMember data = WorldPacket(packet);
bot->GetSession()->HandleGuildDemoteOpcode(data);
}
bool GuildDemoteAction::PlayerIsValid(Player* member)
{
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
@@ -133,12 +123,6 @@ bool GuildRemoveAction::isUseful()
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_REMOVE);
}
void GuildRemoveAction::SendPacket(WorldPacket packet)
{
WorldPackets::Guild::GuildOfficerRemoveMember data = WorldPacket(packet);
bot->GetSession()->HandleGuildRemoveOpcode(data);
}
bool GuildRemoveAction::PlayerIsValid(Player* member)
{
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);