Fix concurrent access issues with ConnectedClients

Replaced direct access to GameMain.Server.ConnectedClients with array snapshots in multiple server-side classes to prevent concurrent modification issues during parallel updates. Also updated PhysicsBody and LevelTrigger to avoid static/shared state in parallel contexts, improving thread safety and reliability.
This commit is contained in:
Eero
2026-01-08 00:26:29 +08:00
parent f4a0d149ca
commit caec44c57d
13 changed files with 108 additions and 59 deletions
@@ -28,11 +28,14 @@ namespace Barotrauma
}
}
// Create snapshot to avoid concurrent access issues during parallel updates
var clients = GameMain.Server.ConnectedClients.ToArray();
if (GameMain.Server is { ServerSettings.RespawnMode: RespawnMode.Permadeath } &&
GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign &&
causeOfDeath != CauseOfDeathType.Disconnected)
{
Client ownerClient = GameMain.Server.ConnectedClients.FirstOrDefault(c => c.Character == this);
Client ownerClient = clients.FirstOrDefault(c => c.Character == this);
if (ownerClient != null)
{
ownerClient.SpectateOnly = true;
@@ -51,7 +54,7 @@ namespace Barotrauma
if (HasAbilityFlag(AbilityFlags.RetainExperienceForNewCharacter))
{
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
var ownerClient = clients.FirstOrDefault(c => c.Character == this);
if (ownerClient != null)
{
(GameMain.GameSession?.GameMode as MultiPlayerCampaign)?.SaveExperiencePoints(ownerClient);
@@ -62,7 +65,7 @@ namespace Barotrauma
if (CauseOfDeath.Killer != null && CauseOfDeath.Killer.IsTraitor && CauseOfDeath.Killer != this)
{
var owner = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
var owner = clients.FirstOrDefault(c => c.Character == this);
if (owner != null)
{
if (!GameMain.LuaCs.Game.overrideTraitors)
@@ -71,7 +74,7 @@ namespace Barotrauma
}
}
}
foreach (Client client in GameMain.Server.ConnectedClients)
foreach (Client client in clients)
{
if (client.InGame)
{
@@ -490,7 +490,9 @@ namespace Barotrauma
case ControlEventData controlEventData:
Client owner = controlEventData.Owner;
msg.WriteBoolean(owner == c && owner.Character == this);
msg.WriteByte(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.SessionId : (byte)0);
// Create snapshot to avoid concurrent access issues during parallel updates
var connectedClients = GameMain.Server.ConnectedClients.ToArray();
msg.WriteByte(owner != null && owner.Character == this && connectedClients.Contains(owner) ? owner.SessionId : (byte)0);
msg.WriteBoolean(info is { RenamingEnabled: true });
break;
case CharacterStatusEventData statusEventData:
@@ -746,7 +748,9 @@ namespace Barotrauma
return;
}
Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this && (!c.SpectateOnly || !GameMain.Server.ServerSettings.AllowSpectating));
// Create snapshot to avoid concurrent access issues during parallel updates
var clients = GameMain.Server.ConnectedClients.ToArray();
Client ownerClient = clients.FirstOrDefault(c => c.Character == this && (!c.SpectateOnly || !GameMain.Server.ServerSettings.AllowSpectating));
if (ownerClient != null)
{
msg.WriteBoolean(true);