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:
@@ -82,10 +82,13 @@ namespace Barotrauma
|
||||
|
||||
private bool IsBlockedByAnotherConversation(IEnumerable<Entity> targets, float duration)
|
||||
{
|
||||
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||
|
||||
if (targets == null || targets.None())
|
||||
{
|
||||
//if the action doesn't target anyone in specific, it's shown to every client
|
||||
foreach (var client in GameMain.Server.ConnectedClients)
|
||||
foreach (var client in clients)
|
||||
{
|
||||
if (IsBlockedByAnotherConversation(client, duration)) { return true; }
|
||||
}
|
||||
@@ -95,7 +98,7 @@ namespace Barotrauma
|
||||
foreach (Entity e in targets)
|
||||
{
|
||||
if (e is not Character character || !character.IsRemotePlayer) { continue; }
|
||||
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
Client targetClient = clients.FirstOrDefault(c => c.Character == character);
|
||||
if (targetClient != null && IsBlockedByAnotherConversation(targetClient, duration)) { return true; }
|
||||
}
|
||||
}
|
||||
@@ -117,13 +120,16 @@ namespace Barotrauma
|
||||
partial void ShowDialog(Character speaker, Character targetCharacter)
|
||||
{
|
||||
targetClients.Clear();
|
||||
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||
|
||||
if (!TargetTag.IsEmpty)
|
||||
{
|
||||
IEnumerable<Entity> entities = ParentEvent.GetTargets(TargetTag);
|
||||
foreach (Entity e in entities)
|
||||
{
|
||||
if (e is not Character character || !character.IsRemotePlayer) { continue; }
|
||||
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
Client targetClient = clients.FirstOrDefault(c => c.Character == character);
|
||||
if (targetClient != null)
|
||||
{
|
||||
targetClients.Add(targetClient);
|
||||
@@ -135,7 +141,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
if (CanClientReceive(c))
|
||||
{
|
||||
|
||||
@@ -9,48 +9,52 @@ namespace Barotrauma;
|
||||
|
||||
partial class EventLogAction : EventAction
|
||||
{
|
||||
partial void AddEntryProjSpecific(EventLog? eventLog, string displayText)
|
||||
partial void AddEntryProjSpecific(EventLog? eventLog, string displayText)
|
||||
{
|
||||
if (eventLog == null) { return; }
|
||||
|
||||
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||
|
||||
if (!TargetTag.IsEmpty)
|
||||
{
|
||||
if (eventLog == null) { return; }
|
||||
if (!TargetTag.IsEmpty)
|
||||
List<Client> targetClients = new List<Client>();
|
||||
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
List<Client> targetClients = new List<Client>();
|
||||
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
||||
if (target is Character character)
|
||||
{
|
||||
if (target is Character character)
|
||||
var ownerClient = clients.FirstOrDefault(c => c.Character == character);
|
||||
if (ownerClient != null)
|
||||
{
|
||||
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
if (ownerClient != null)
|
||||
{
|
||||
targetClients.Add(ownerClient);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"{target} is not a valid target for an EventLogAction. The target should be a character.",
|
||||
ParentEvent.Prefab.ContentPackage);
|
||||
targetClients.Add(ownerClient);
|
||||
}
|
||||
}
|
||||
if (eventLog!.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, targetClients) && ShowInServerLog)
|
||||
else
|
||||
{
|
||||
Log(targetClients);
|
||||
DebugConsole.AddWarning($"{target} is not a valid target for an EventLogAction. The target should be a character.",
|
||||
ParentEvent.Prefab.ContentPackage);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (eventLog!.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, targetClients) && ShowInServerLog)
|
||||
{
|
||||
if (eventLog.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, GameMain.Server.ConnectedClients) && ShowInServerLog)
|
||||
{
|
||||
Log(targetClients: null);
|
||||
}
|
||||
}
|
||||
|
||||
void Log(List<Client>? targetClients)
|
||||
{
|
||||
string clientStr = targetClients == null || targetClients.None() ?
|
||||
string.Empty :
|
||||
$" ({string.Join(", ", targetClients.Select(c => NetworkMember.ClientLogName(c)))})";
|
||||
GameServer.Log($"Event \"{ParentEvent.Prefab.Name}\"{clientStr}: " + displayText,
|
||||
ParentEvent is TraitorEvent ? ServerLog.MessageType.Traitors : ServerLog.MessageType.Chat);
|
||||
Log(targetClients);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (eventLog.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, clients) && ShowInServerLog)
|
||||
{
|
||||
Log(targetClients: null);
|
||||
}
|
||||
}
|
||||
|
||||
void Log(List<Client>? targetClients)
|
||||
{
|
||||
string clientStr = targetClients == null || targetClients.None() ?
|
||||
string.Empty :
|
||||
$" ({string.Join(", ", targetClients.Select(c => NetworkMember.ClientLogName(c)))})";
|
||||
GameServer.Log($"Event \"{ParentEvent.Prefab.Name}\"{clientStr}: " + displayText,
|
||||
ParentEvent is TraitorEvent ? ServerLog.MessageType.Traitors : ServerLog.MessageType.Chat);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-3
@@ -1,3 +1,5 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EventObjectiveAction : EventAction
|
||||
@@ -13,9 +15,12 @@ namespace Barotrauma
|
||||
ParentObjectiveId,
|
||||
CanBeCompleted);
|
||||
|
||||
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||
|
||||
if (TargetTag.IsEmpty)
|
||||
{
|
||||
foreach (var client in GameMain.Server.ConnectedClients)
|
||||
foreach (var client in clients)
|
||||
{
|
||||
if (client.Character == null) { continue; }
|
||||
EventManager.ServerWriteObjective(client, objective);
|
||||
@@ -26,11 +31,11 @@ namespace Barotrauma
|
||||
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
if (target is not Character character) { continue; }
|
||||
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
var ownerClient = clients.FirstOrDefault(c => c.Character == character);
|
||||
if (ownerClient == null) { continue; }
|
||||
EventManager.ServerWriteObjective(ownerClient, objective);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ partial class HighlightAction : EventAction
|
||||
IEnumerable<Client>? targetClients = null;
|
||||
if (targetCharacters != null)
|
||||
{
|
||||
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||
targetClients = targetCharacters
|
||||
.Select(c => GameMain.Server.ConnectedClients.FirstOrDefault(client => client.Character == c))
|
||||
.Select(c => clients.FirstOrDefault(client => client.Character == c))
|
||||
.Where(c => c != null)!;
|
||||
}
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.SetHighlightEventData(State, highlightColor, targetClients));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -22,7 +23,9 @@ namespace Barotrauma
|
||||
|
||||
private static void NotifyMissionUnlock(Mission mission)
|
||||
{
|
||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
||||
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||
foreach (Client client in clients)
|
||||
{
|
||||
NotifyMissionUnlock(mission, client);
|
||||
}
|
||||
@@ -40,4 +43,4 @@ namespace Barotrauma
|
||||
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,4 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user