Skip to content

Commit e21c0e4

Browse files
fix: client and server disconnect warnings (#3786)
* fix This fix prevents the warning message from being logged on the client side when shutting down from the client side and the developer log level is set. This fix also assures that a server or host does not send itself a disconnect message. This fix separated the transport id clean up between client and host/server upon handling a disconnect event which assures the server/host will have access to this transport id when processing a client that disconnected. * update Adding change log entries. * test Adding test to check that no warning messages are logged upon a client disconnecting itself from a session. * refactor and fix Fixing several issues with the disconnect flow. Not removing the transport id until later. The server now only does this within OnClientDisconnectFromServer. When disconnecting a client with a reason, the client is not completely disconnected until the end of the frame when the server has finished sending all pending messages. Fixing some missed MessageDeliveryType replacements. Removing the MockSkippingApproval as it is no longer needed with the fixes in place. * test Did a bunch of cleanup on several connection tests to improve stability and use more recent integration test features. ClientOnlyConnectionTests reduced the transport connection timeout and increased the timeout helper. ConnectionApprovalTimeoutTests needed to have the server and client timeout values vary depending upon the test type. PeerDisconnectCallbackTests needed to trap for the peer disconnecting. * style Removing whitespaces * update adding one more change log entry
1 parent b56e216 commit e21c0e4

File tree

9 files changed

+241
-139
lines changed

9 files changed

+241
-139
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ Additional documentation and release notes are available at [Multiplayer Documen
1919
### Changed
2020

2121
- First pass of CoreCLR engine API changes. (#3799)
22-
- Changed NetworkAnimator to use the `RpcAttribute` along with the appropriate `SendTo` parameter. (#3586)
22+
- Changed when a server is disconnecting a client with a reason it now defers the complete transport disconnect sequence until the end of the frame after the server's transport has sent all pending outbound messages. (#3786)
2323
- Improve performance of `NetworkTransformState`. (#3770)
24-
24+
- Changed NetworkAnimator to use the `RpcAttribute` along with the appropriate `SendTo` parameter. (#3586)
2525

2626
### Deprecated
2727

@@ -32,6 +32,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
3232
### Fixed
3333

3434
- Fixed issues with the "Client-server quickstart for Netcode for GameObjects" script having static methods and properties. (#3787)
35+
- Fixed issue where a warning message was being logged upon a client disconnecting from a server when the log level is set to developer. (#3786)
36+
- Fixed issue where the server or host would no longer have access to the transport id to client id table when processing a transport level client disconnect event. (#3786)
3537
- Fixed issue where invoking an RPC, on another `NetworkBehaviour` associated with the same `NetworkObject` that is ordered before the `NetworkBehaviour` invoking the RPC, during `OnNetworkSpawn` could throw an exception if scene management is disabled. (#3782)
3638
- Fixed issue where the `Axis to Synchronize` toggles didn't work with multi object editing in `NetworkTransform`. (#3781)
3739
- Fixed issue where using the dedicated server package would override all attempts to change the port by code. (#3760)

com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs

Lines changed: 90 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,6 @@ internal void RemovePendingClient(ulong clientId)
362362
return (clientId, true);
363363
}
364364

365-
if (NetworkLog.CurrentLogLevel == LogLevel.Developer)
366-
{
367-
NetworkLog.LogWarning($"Trying to get the NGO client ID map for the transport ID ({transportId}) but did not find the map entry! Returning default transport ID value.");
368-
}
369-
370365
return (default, false);
371366
}
372367

@@ -488,6 +483,15 @@ internal void HandleNetworkEvent(NetworkEvent networkEvent, ulong transportClien
488483
}
489484
}
490485

486+
/// <summary>
487+
/// Client's save their assigned transport id.
488+
/// </summary>
489+
/// <remarks>
490+
/// Added to be able to appropriately log the client's transport
491+
/// id when it is shutdown or disconnected.
492+
/// </remarks>
493+
private ulong m_LocalClientTransportId;
494+
491495
/// <summary>
492496
/// Handles a <see cref="NetworkEvent.Connect"/> event.
493497
/// </summary>
@@ -508,6 +512,8 @@ internal void ConnectEventHandler(ulong transportClientId)
508512
}
509513
else
510514
{
515+
// Cache the local client's transport id.
516+
m_LocalClientTransportId = transportClientId;
511517
clientId = NetworkManager.ServerClientId;
512518
}
513519

@@ -585,9 +591,14 @@ private void GenerateDisconnectInformation(ulong clientId, ulong transportClient
585591
/// </summary>
586592
internal void DisconnectEventHandler(ulong transportClientId)
587593
{
588-
var (clientId, wasConnectedClient) = TransportIdCleanUp(transportClientId);
589-
if (!wasConnectedClient)
594+
// Check to see if the client has already been removed from the table but
595+
// do not remove it just yet.
596+
var (clientId, isConnectedClient) = TransportIdToClientId(transportClientId);
597+
598+
// If the client is not registered and we are the server
599+
if (!isConnectedClient && NetworkManager.IsServer)
590600
{
601+
// Then exit early
591602
return;
592603
}
593604

@@ -622,17 +633,12 @@ internal void DisconnectEventHandler(ulong transportClientId)
622633
{
623634
// We need to process the disconnection before notifying
624635
OnClientDisconnectFromServer(clientId);
625-
626-
// Now notify the client has disconnected
627-
InvokeOnClientDisconnectCallback(clientId);
628-
629-
if (LocalClient.IsHost)
630-
{
631-
InvokeOnPeerDisconnectedCallback(clientId);
632-
}
633636
}
634637
else
635638
{
639+
// Client's clean up their transport id separately from the server.
640+
TransportIdCleanUp(transportClientId);
641+
636642
// Notify local client of disconnection
637643
InvokeOnClientDisconnectCallback(clientId);
638644

@@ -793,12 +799,15 @@ private IEnumerator ApprovalTimeout(ulong clientId)
793799
/// </summary>
794800
internal void ApproveConnection(ref ConnectionRequestMessage connectionRequestMessage, ref NetworkContext context)
795801
{
802+
if (ConnectionApprovalCallback == null)
803+
{
804+
return;
805+
}
796806
// Note: Delegate creation allocates.
797807
// Note: ToArray() also allocates. :(
798808
var response = new NetworkManager.ConnectionApprovalResponse();
799809
ClientsToApprove[context.SenderId] = response;
800-
801-
ConnectionApprovalCallback(
810+
ConnectionApprovalCallback?.Invoke(
802811
new NetworkManager.ConnectionApprovalRequest
803812
{
804813
Payload = connectionRequestMessage.ConnectionData,
@@ -852,13 +861,6 @@ internal void ProcessPendingApprovals()
852861
}
853862
}
854863

855-
/// <summary>
856-
/// Adding this because message hooks cannot happen fast enough under certain scenarios
857-
/// where the message is sent and responded to before the hook is in place.
858-
/// </summary>
859-
internal bool MockSkippingApproval;
860-
861-
862864
/// <summary>
863865
/// Server Side: Handles the denial of a client who sent a connection request
864866
/// </summary>
@@ -873,12 +875,36 @@ private void HandleConnectionDisconnect(ulong ownerClientId, string reason = "")
873875
{
874876
Reason = reason
875877
};
876-
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId);
878+
SendMessage(ref disconnectReason, MessageDeliveryType<DisconnectReasonMessage>.DefaultDelivery, ownerClientId);
879+
m_ClientsToDisconnect.Add(ownerClientId);
880+
return;
877881
}
878882

879883
DisconnectRemoteClient(ownerClientId);
880884
}
881885

886+
private List<ulong> m_ClientsToDisconnect = new List<ulong>();
887+
888+
internal void ProcessClientsToDisconnect()
889+
{
890+
if (m_ClientsToDisconnect.Count == 0)
891+
{
892+
return;
893+
}
894+
foreach (var clientId in m_ClientsToDisconnect)
895+
{
896+
try
897+
{
898+
DisconnectRemoteClient(clientId);
899+
}
900+
catch (Exception ex)
901+
{
902+
Debug.LogException(ex);
903+
}
904+
}
905+
m_ClientsToDisconnect.Clear();
906+
}
907+
882908
/// <summary>
883909
/// Server Side: Handles the approval of a client
884910
/// </summary>
@@ -939,12 +965,6 @@ internal void HandleConnectionApproval(ulong ownerClientId, bool createPlayerObj
939965
// Server doesn't send itself the connection approved message
940966
if (ownerClientId != NetworkManager.ServerClientId)
941967
{
942-
if (MockSkippingApproval)
943-
{
944-
NetworkLog.LogInfo("Mocking server not responding with connection approved...");
945-
return;
946-
}
947-
948968
SendConnectionApprovedMessage(ownerClientId);
949969

950970
// If scene management is disabled, then we are done and notify the local host-server the client is connected
@@ -1040,7 +1060,7 @@ private void SendConnectionApprovedMessage(ulong approvedClientId)
10401060
}
10411061
}
10421062

1043-
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, approvedClientId);
1063+
SendMessage(ref message, MessageDeliveryType<ConnectionApprovedMessage>.DefaultDelivery, approvedClientId);
10441064

10451065
message.MessageVersions.Dispose();
10461066
message.ConnectedClientIds.Dispose();
@@ -1111,13 +1131,9 @@ internal NetworkClient AddClient(ulong clientId)
11111131
return ConnectedClients[clientId];
11121132
}
11131133

1114-
var networkClient = LocalClient;
1115-
11161134
// If this is not the local client then create a new one
1117-
if (clientId != NetworkManager.LocalClientId)
1118-
{
1119-
networkClient = new NetworkClient();
1120-
}
1135+
var networkClient = clientId == NetworkManager.LocalClientId ? LocalClient : new NetworkClient();
1136+
11211137
networkClient.SetRole(clientId == NetworkManager.ServerClientId, isClient: true, NetworkManager);
11221138
networkClient.ClientId = clientId;
11231139
if (!ConnectedClients.ContainsKey(clientId))
@@ -1224,6 +1240,14 @@ internal void OnClientDisconnectFromServer(ulong clientId)
12241240
// clean up as everything that needs to be destroyed will be during shutdown.
12251241
if (NetworkManager.ShutdownInProgress && clientId == NetworkManager.ServerClientId)
12261242
{
1243+
// Now notify the client has disconnected.
1244+
// (transport id cleanup is handled within)
1245+
InvokeOnClientDisconnectCallback(clientId);
1246+
1247+
if (LocalClient.IsHost)
1248+
{
1249+
InvokeOnPeerDisconnectedCallback(clientId);
1250+
}
12271251
return;
12281252
}
12291253

@@ -1392,8 +1416,20 @@ internal void OnClientDisconnectFromServer(ulong clientId)
13921416
}
13931417

13941418
ConnectedClientIds.Remove(clientId);
1395-
var message = new ClientDisconnectedMessage { ClientId = clientId };
1396-
MessageManager?.SendMessage(ref message, MessageDeliveryType<ClientDisconnectedMessage>.DefaultDelivery, ConnectedClientIds);
1419+
1420+
if (MessageManager != null)
1421+
{
1422+
var message = new ClientDisconnectedMessage { ClientId = clientId };
1423+
foreach (var sendToId in ConnectedClientIds)
1424+
{
1425+
// Do not send a disconnect message to ourself
1426+
if (sendToId == NetworkManager.LocalClientId)
1427+
{
1428+
continue;
1429+
}
1430+
MessageManager.SendMessage(ref message, MessageDeliveryType<ClientDisconnectedMessage>.DefaultDelivery, sendToId);
1431+
}
1432+
}
13971433

13981434
// Used for testing/validation purposes only
13991435
// Promote a new session owner when the ENABLE_DAHOST_AUTOPROMOTE_SESSION_OWNER scripting define is set
@@ -1404,17 +1440,18 @@ internal void OnClientDisconnectFromServer(ulong clientId)
14041440
var (transportId, idExists) = ClientIdToTransportId(clientId);
14051441
if (idExists)
14061442
{
1407-
NetworkManager.NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
1408-
1409-
InvokeOnClientDisconnectCallback(clientId);
1410-
1411-
if (LocalClient.IsHost)
1443+
// Clean up the transport to client (and vice versa) mappings
1444+
var (transportIdDisconnected, wasRemoved) = TransportIdCleanUp(transportId);
1445+
if (wasRemoved)
14121446
{
1413-
InvokeOnPeerDisconnectedCallback(clientId);
1414-
}
1447+
NetworkManager.NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
1448+
InvokeOnClientDisconnectCallback(clientId);
14151449

1416-
// Clean up the transport to client (and vice versa) mappings
1417-
TransportIdCleanUp(transportId);
1450+
if (LocalClient.IsHost)
1451+
{
1452+
InvokeOnPeerDisconnectedCallback(clientId);
1453+
}
1454+
}
14181455
}
14191456

14201457
// Assure the client id is no longer in the pending clients list
@@ -1462,16 +1499,6 @@ internal void DisconnectClient(ulong clientId, string reason = null)
14621499
return;
14631500
}
14641501

1465-
if (!string.IsNullOrEmpty(reason))
1466-
{
1467-
var disconnectReason = new DisconnectReasonMessage
1468-
{
1469-
Reason = reason
1470-
};
1471-
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId);
1472-
}
1473-
1474-
Transport.ClosingRemoteConnection();
14751502
var transportId = ClientIdToTransportId(clientId);
14761503
if (transportId.Item2)
14771504
{
@@ -1491,6 +1518,7 @@ internal void DisconnectClient(ulong clientId, string reason = null)
14911518
internal void Initialize(NetworkManager networkManager)
14921519
{
14931520
// Prepare for a new session
1521+
m_LocalClientTransportId = 0;
14941522
LocalClient.IsApproved = false;
14951523
m_PendingClients.Clear();
14961524
ConnectedClients.Clear();
@@ -1524,8 +1552,9 @@ internal void Shutdown()
15241552
{
15251553
Transport.ShuttingDown();
15261554
var clientId = NetworkManager ? NetworkManager.LocalClientId : NetworkManager.ServerClientId;
1527-
var transportId = ClientIdToTransportId(clientId);
1528-
GenerateDisconnectInformation(clientId, transportId.Item1, $"{nameof(NetworkConnectionManager)} was shutdown.");
1555+
// Server and host just log 0 for their transport id while clients will log their cached m_LocalClientTransportId
1556+
var transportId = clientId == NetworkManager.ServerClientId ? 0 : m_LocalClientTransportId;
1557+
GenerateDisconnectInformation(clientId, transportId, $"{nameof(NetworkConnectionManager)} was shutdown.");
15291558
}
15301559

15311560
if (LocalClient.IsServer)

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,6 @@ public void NetworkUpdate(NetworkUpdateStage updateStage)
346346
AnticipationSystem.SetupForUpdate();
347347
MessageManager.ProcessIncomingMessageQueue();
348348

349-
MessageManager.CleanupDisconnectedClients();
350349
AnticipationSystem.ProcessReanticipation();
351350
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
352351
foreach (var networkObjectEntry in NetworkTransformFixedUpdate)
@@ -478,6 +477,18 @@ public void NetworkUpdate(NetworkUpdateStage updateStage)
478477
// This is "ok" to invoke when not processing messages since it is just cleaning up messages that never got handled within their timeout period.
479478
DeferredMessageManager.CleanupStaleTriggers();
480479

480+
if (IsServer)
481+
{
482+
// Process any pending clients that need to be disconnected.
483+
// This is typically a disconnect with reason scenario where
484+
// we want the disconnect reason message to be sent prior to
485+
// completely shutting down the endpoint.
486+
ConnectionManager.ProcessClientsToDisconnect();
487+
}
488+
489+
// Clean up disconnected clients last
490+
MessageManager.CleanupDisconnectedClients();
491+
481492
if (m_ShuttingDown)
482493
{
483494
// Host-server will disconnect any connected clients prior to finalizing its shutdown

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,11 @@ private void CleanupDisconnectedClient(ulong clientId)
492492

493493
internal void CleanupDisconnectedClients()
494494
{
495+
if (m_DisconnectedClients.Count == 0)
496+
{
497+
return;
498+
}
499+
495500
foreach (var clientId in m_DisconnectedClients)
496501
{
497502
CleanupDisconnectedClient(clientId);

com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ public void Setup()
3232
// Default is 1000ms per connection attempt and 60 connection attempts (60s)
3333
// Currently there is no easy way to set these values other than in-editor
3434
var unityTransport = m_NetworkManagerGameObject.AddComponent<UnityTransport>();
35-
unityTransport.ConnectTimeoutMS = 1000;
35+
unityTransport.ConnectTimeoutMS = 500;
3636
unityTransport.MaxConnectAttempts = 1;
37-
m_TimeoutHelper = new TimeoutHelper(2);
37+
m_TimeoutHelper = new TimeoutHelper(4);
3838
m_ClientNetworkManager.NetworkConfig.NetworkTransport = unityTransport;
3939
}
4040

@@ -49,7 +49,6 @@ public IEnumerator ClientFailsToConnect()
4949

5050
// Unity Transport throws an error when it times out
5151
LogAssert.Expect(LogType.Error, "Failed to connect to server.");
52-
5352
yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => m_WasDisconnected, m_TimeoutHelper);
5453
Assert.False(m_TimeoutHelper.TimedOut, "Timed out waiting for client to timeout waiting to connect!");
5554

0 commit comments

Comments
 (0)