Skip to content

Commit 509a619

Browse files
authored
fix: Error when a client is disconnected as a result of attempting to send to it [MTT-4607] (#2495)
* fix: Error when a client is disconnected as a result of attempting to send to it * Changelog
1 parent 0e02dc7 commit 509a619

File tree

4 files changed

+126
-16
lines changed

4 files changed

+126
-16
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ Additional documentation and release notes are available at [Multiplayer Documen
88

99
## [Unreleased]
1010

11+
### Fixed
12+
13+
- Fixed some errors that could occur if a connection is lost and the loss is detected when attempting to write to the socket. (#2495)
14+
15+
## [1.4.0]
16+
1117
### Added
1218

1319
- Added a way to access the GlobalObjectIdHash via PrefabIdHash for use in the Connection Approval Callback. (#2437)

com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA
5858
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
5959
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
6060

61+
private HashSet<ulong> m_DisconnectedClients = new HashSet<ulong>();
62+
6163
// This is m_PerClientMessageVersion[clientId][messageType] = version
6264
private Dictionary<ulong, Dictionary<Type, int>> m_PerClientMessageVersions = new Dictionary<ulong, Dictionary<Type, int>>();
6365
private Dictionary<uint, Type> m_MessagesByHash = new Dictionary<uint, Type>();
@@ -155,8 +157,9 @@ public unsafe void Dispose()
155157
// from the queue.
156158
foreach (var kvp in m_SendQueues)
157159
{
158-
CleanupDisconnectedClient(kvp.Key);
160+
ClientDisconnected(kvp.Key);
159161
}
162+
CleanupDisconnectedClients();
160163

161164
for (var queueIndex = 0; queueIndex < m_IncomingMessageQueue.Length; ++queueIndex)
162165
{
@@ -464,41 +467,37 @@ internal void ClientConnected(ulong clientId)
464467
}
465468

466469
internal void ClientDisconnected(ulong clientId)
470+
{
471+
m_DisconnectedClients.Add(clientId);
472+
}
473+
474+
private void CleanupDisconnectedClient(ulong clientId)
467475
{
468476
if (!m_SendQueues.ContainsKey(clientId))
469477
{
470478
return;
471479
}
472-
CleanupDisconnectedClient(clientId);
473-
m_SendQueues.Remove(clientId);
474-
}
475480

476-
private void CleanupDisconnectedClient(ulong clientId)
477-
{
478481
var queue = m_SendQueues[clientId];
479482
for (var i = 0; i < queue.Length; ++i)
480483
{
481484
queue.ElementAt(i).Writer.Dispose();
482485
}
483486

484487
queue.Dispose();
488+
m_SendQueues.Remove(clientId);
489+
490+
m_PerClientMessageVersions.Remove(clientId);
485491
}
486492

487493
internal void CleanupDisconnectedClients()
488494
{
489-
var removeList = new NativeList<ulong>(Allocator.Temp);
490-
foreach (var clientId in m_PerClientMessageVersions.Keys)
495+
foreach (var clientId in m_DisconnectedClients)
491496
{
492-
if (!m_SendQueues.ContainsKey(clientId))
493-
{
494-
removeList.Add(clientId);
495-
}
497+
CleanupDisconnectedClient(clientId);
496498
}
497499

498-
foreach (var clientId in removeList)
499-
{
500-
m_PerClientMessageVersions.Remove(clientId);
501-
}
500+
m_DisconnectedClients.Clear();
502501
}
503502

504503
public static int CreateMessageAndGetVersion<T>() where T : INetworkMessage, new()
@@ -637,6 +636,10 @@ internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter t
637636

638637
for (var i = 0; i < clientIds.Count; ++i)
639638
{
639+
if (m_DisconnectedClients.Contains(clientIds[i]))
640+
{
641+
continue;
642+
}
640643
var messageVersion = 0;
641644
// Special case because this is the message that carries the version info - thus the version info isn't
642645
// populated yet when we get this. The first part of this message always has to be the version data
@@ -788,6 +791,14 @@ internal unsafe void ProcessSendQueues()
788791
for (var i = 0; i < sendQueueItem.Length; ++i)
789792
{
790793
ref var queueItem = ref sendQueueItem.ElementAt(i);
794+
// this is checked at every iteration because
795+
// 1) each writer needs to be disposed, so we have to do the full loop regardless, and
796+
// 2) the call to m_MessageSender.Send() may result in calling ClientDisconnected(), so the result of this check may change partway through iteration
797+
if (m_DisconnectedClients.Contains(clientId))
798+
{
799+
queueItem.Writer.Dispose();
800+
continue;
801+
}
791802
if (queueItem.BatchHeader.BatchCount == 0)
792803
{
793804
queueItem.Writer.Dispose();
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.Collections.Generic;
2+
using NUnit.Framework;
3+
4+
namespace Unity.Netcode.EditorTests
5+
{
6+
public class DisconnectOnSendTests
7+
{
8+
private struct TestMessage : INetworkMessage, INetworkSerializeByMemcpy
9+
{
10+
public void Serialize(FastBufferWriter writer, int targetVersion)
11+
{
12+
}
13+
14+
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
15+
{
16+
return true;
17+
}
18+
19+
public void Handle(ref NetworkContext context)
20+
{
21+
}
22+
23+
public int Version => 0;
24+
}
25+
26+
private class DisconnectOnSendMessageSender : IMessageSender
27+
{
28+
public MessagingSystem MessagingSystem;
29+
30+
public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData)
31+
{
32+
MessagingSystem.ClientDisconnected(clientId);
33+
}
34+
}
35+
private class TestMessageProvider : IMessageProvider
36+
{
37+
// Keep track of what we sent
38+
private List<MessagingSystem.MessageWithHandler> m_MessageList = new List<MessagingSystem.MessageWithHandler>{
39+
new MessagingSystem.MessageWithHandler
40+
{
41+
MessageType = typeof(TestMessage),
42+
Handler = MessagingSystem.ReceiveMessage<TestMessage>,
43+
GetVersion = MessagingSystem.CreateMessageAndGetVersion<TestMessage>
44+
}
45+
};
46+
47+
public List<MessagingSystem.MessageWithHandler> GetMessages()
48+
{
49+
return m_MessageList;
50+
}
51+
}
52+
53+
private TestMessageProvider m_TestMessageProvider;
54+
private DisconnectOnSendMessageSender m_MessageSender;
55+
private MessagingSystem m_MessagingSystem;
56+
private ulong[] m_Clients = { 0 };
57+
58+
[SetUp]
59+
public void SetUp()
60+
{
61+
m_MessageSender = new DisconnectOnSendMessageSender();
62+
m_TestMessageProvider = new TestMessageProvider();
63+
m_MessagingSystem = new MessagingSystem(m_MessageSender, this, m_TestMessageProvider);
64+
m_MessageSender.MessagingSystem = m_MessagingSystem;
65+
m_MessagingSystem.ClientConnected(0);
66+
m_MessagingSystem.SetVersion(0, XXHash.Hash32(typeof(TestMessage).FullName), 0);
67+
}
68+
69+
[TearDown]
70+
public void TearDown()
71+
{
72+
m_MessagingSystem.Dispose();
73+
}
74+
75+
private TestMessage GetMessage()
76+
{
77+
return new TestMessage();
78+
}
79+
80+
[Test]
81+
public void WhenDisconnectIsCalledDuringSend_NoErrorsOccur()
82+
{
83+
var message = GetMessage();
84+
m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients);
85+
86+
// This is where an exception would be thrown and logged.
87+
m_MessagingSystem.ProcessSendQueues();
88+
}
89+
}
90+
}

com.unity.netcode.gameobjects/Tests/Editor/Messaging/DisconnectOnSendTests.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)