Skip to content

Commit 924d3da

Browse files
NoelStephensUnityjakobbbb
authored andcommitted
fix: NetworkManager ApprovalTimeout should not depend upon client synchronization (Unity-Technologies#2261)
* fix This fix separates the IsConnectedClient from the approval process by adding an IsApproved flag. This also has a fix to prevent the domain backup error during serialization. * test Added the ability to bypass the entire NetcodeIntegrationTest connection approval process after the server and clients have been started. This allows us to now use NetcodeIntegrationTest for unique connection oriented tests without being bound to the asserts if not all clients connected properly. Refactored for ConnectionApprovalTimeoutTests to use the added m_BypassConnectionTimeout to bypass the waiting for clients to connect. It still uses the message hook catch technique to simulate the timeout scenarios where either a server detects a transport connection but never receives a connection request or a client sends the connection request but never receives approval for the connection.
1 parent 0d7a57a commit 924d3da

6 files changed

Lines changed: 176 additions & 116 deletions

File tree

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
77

88
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
99

10+
## [Unreleased]
11+
12+
### Added
13+
- Added `NetworkManager.IsApproved` flag that is set to `true` a client has been approved.(#2261)
14+
15+
### Changed
16+
17+
18+
### Fixed
19+
20+
- Fixed `NetworkManager.ApprovalTimeout` will not timeout due to slower client synchronization times as it now uses the added `NetworkManager.IsApproved` flag to determined if the client has been approved or not.(#2261)
21+
22+
1023
## [1.1.0] - 2022-10-19
1124

1225
### Added

com.unity.netcode.gameobjects/Components/NetworkAnimator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ private void BuildDestinationToTransitionInfoTable()
232232
private void BuildTransitionStateInfoList()
233233
{
234234
#if UNITY_EDITOR
235+
if (UnityEditor.EditorApplication.isUpdating)
236+
{
237+
return;
238+
}
235239
TransitionStateInfoList = new List<TransitionStateinfo>();
236240
var animatorController = m_Animator.runtimeAnimatorController as AnimatorController;
237241
if (animatorController == null)

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

Lines changed: 67 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,22 @@ public IReadOnlyList<ulong> ConnectedClientsIds
366366
public bool IsListening { get; internal set; }
367367

368368
/// <summary>
369-
/// Gets if we are connected as a client
369+
/// When true, the client is connected, approved, and synchronized with
370+
/// the server.
370371
/// </summary>
371372
public bool IsConnectedClient { get; internal set; }
372373

374+
/// <summary>
375+
/// Is true when the client has been approved.
376+
/// </summary>
377+
/// <remarks>
378+
/// This only reflects the client's approved status and does not mean the client
379+
/// has finished the connection and synchronization process. The server-host will
380+
/// always be approved upon being starting the <see cref="NetworkManager"/>
381+
/// <see cref="IsConnectedClient"/>
382+
/// </remarks>
383+
public bool IsApproved { get; internal set; }
384+
373385
/// <summary>
374386
/// Can be used to determine if the <see cref="NetworkManager"/> is currently shutting itself down
375387
/// </summary>
@@ -882,6 +894,8 @@ private void Initialize(bool server)
882894
return;
883895
}
884896

897+
IsApproved = false;
898+
885899
ComponentFactory.SetDefaults();
886900

887901
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
@@ -1025,7 +1039,6 @@ public bool StartServer()
10251039
}
10261040

10271041
Initialize(true);
1028-
10291042
IsServer = true;
10301043
IsClient = false;
10311044
IsListening = true;
@@ -1038,6 +1051,7 @@ public bool StartServer()
10381051
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
10391052

10401053
OnServerStarted?.Invoke();
1054+
IsApproved = true;
10411055
return true;
10421056
}
10431057
else
@@ -1159,6 +1173,7 @@ public bool StartHost()
11591173
}
11601174

11611175
response.Approved = true;
1176+
IsApproved = true;
11621177
HandleConnectionApproval(ServerClientId, response);
11631178
}
11641179
else
@@ -1420,6 +1435,7 @@ internal void ShutdownInternal()
14201435
}
14211436

14221437
IsConnectedClient = false;
1438+
IsApproved = false;
14231439

14241440
// We need to clean up NetworkObjects before we reset the IsServer
14251441
// and IsClient properties. This provides consistency of these two
@@ -1656,59 +1672,71 @@ private void SendConnectionRequest()
16561672

16571673
private IEnumerator ApprovalTimeout(ulong clientId)
16581674
{
1659-
if (IsServer)
1675+
var timeStarted = IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup;
1676+
var timedOut = false;
1677+
var connectionApproved = false;
1678+
var connectionNotApproved = false;
1679+
var timeoutMarker = timeStarted + NetworkConfig.ClientConnectionBufferTimeout;
1680+
1681+
while (IsListening && !ShutdownInProgress && !timedOut && !connectionApproved)
16601682
{
1661-
NetworkTime timeStarted = LocalTime;
1683+
yield return null;
1684+
// Check if we timed out
1685+
timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup);
16621686

1663-
//We yield every frame incase a pending client disconnects and someone else gets its connection id
1664-
while (IsListening && (LocalTime - timeStarted).Time < NetworkConfig.ClientConnectionBufferTimeout && PendingClients.ContainsKey(clientId))
1687+
if (IsServer)
16651688
{
1666-
yield return null;
1667-
}
1689+
// When the client is no longer in the pending clients list and is in the connected clients list
1690+
// it has been approved
1691+
connectionApproved = !PendingClients.ContainsKey(clientId) && ConnectedClients.ContainsKey(clientId);
16681692

1669-
if (!IsListening)
1670-
{
1671-
yield break;
1693+
// For the server side, if the client is in neither list then it was declined or the client disconnected
1694+
connectionNotApproved = !PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId);
16721695
}
1673-
1674-
if (PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId))
1696+
else
16751697
{
1676-
// Timeout
1677-
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
1678-
{
1679-
NetworkLog.LogInfo($"Client {clientId} Handshake Timed Out");
1680-
}
1681-
1682-
DisconnectClient(clientId);
1698+
connectionApproved = IsApproved;
16831699
}
16841700
}
1685-
else
1701+
1702+
// Exit coroutine if we are no longer listening or a shutdown is in progress (client or server)
1703+
if (!IsListening || ShutdownInProgress)
16861704
{
1687-
float timeStarted = Time.realtimeSinceStartup;
1688-
//We yield every frame in case a pending client disconnects and someone else gets its connection id
1689-
while (IsListening && (Time.realtimeSinceStartup - timeStarted) < NetworkConfig.ClientConnectionBufferTimeout && !IsConnectedClient)
1690-
{
1691-
yield return null;
1692-
}
1705+
yield break;
1706+
}
16931707

1694-
if (!IsConnectedClient && NetworkLog.CurrentLogLevel <= LogLevel.Normal)
1708+
// If the client timed out or was not approved
1709+
if (timedOut || connectionNotApproved)
1710+
{
1711+
// Timeout
1712+
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
16951713
{
1696-
// TODO: Possibly add a callback for users to be notified of this condition here?
1697-
NetworkLog.LogWarning($"[ApprovalTimeout] Client timed out! You might need to increase the {nameof(NetworkConfig.ClientConnectionBufferTimeout)} duration. Approval Check Start: {timeStarted} | Approval Check Stopped: {Time.realtimeSinceStartup}");
1714+
if (timedOut)
1715+
{
1716+
if (IsServer)
1717+
{
1718+
// Log a warning that the transport detected a connection but then did not receive a follow up connection request message.
1719+
// (hacking or something happened to the server's network connection)
1720+
NetworkLog.LogWarning($"Server detected a transport connection from Client-{clientId}, but timed out waiting for the connection request message.");
1721+
}
1722+
else
1723+
{
1724+
// We only provide informational logging for the client side
1725+
NetworkLog.LogInfo("Timed out waiting for the server to approve the connection request.");
1726+
}
1727+
}
1728+
else if (connectionNotApproved)
1729+
{
1730+
NetworkLog.LogInfo($"Client-{clientId} was either denied approval or disconnected while being approved.");
1731+
}
16981732
}
16991733

1700-
if (!IsListening)
1734+
if (IsServer)
17011735
{
1702-
yield break;
1736+
DisconnectClient(clientId);
17031737
}
1704-
1705-
if (!IsConnectedClient)
1738+
else
17061739
{
1707-
// Timeout
1708-
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
1709-
{
1710-
NetworkLog.LogInfo("Server Handshake Timed Out");
1711-
}
17121740
Shutdown(true);
17131741
}
17141742
}

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public void Handle(ref NetworkContext context)
7979
networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime);
8080

8181
networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId };
82+
networkManager.IsApproved = true;
8283

8384
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
8485
if (!networkManager.NetworkConfig.EnableSceneManagement)

com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ public enum HostOrServer
131131

132132
protected bool m_EnableVerboseDebug { get; set; }
133133

134+
/// <summary>
135+
/// When set to true, this will bypass the entire
136+
/// wait for clients to connect process.
137+
/// </summary>
138+
/// <remarks>
139+
/// CAUTION:
140+
/// Setting this to true will bypass other helper
141+
/// identification related code, so this should only
142+
/// be used for connection failure oriented testing
143+
/// </remarks>
144+
protected bool m_BypassConnectionTimeout { get; set; }
145+
134146
/// <summary>
135147
/// Used to display the various integration test
136148
/// stages and can be used to log verbose information
@@ -455,31 +467,36 @@ protected IEnumerator StartServerAndClients()
455467
// Notification that the server and clients have been started
456468
yield return OnStartedServerAndClients();
457469

458-
// Wait for all clients to connect
459-
yield return WaitForClientsConnectedOrTimeOut();
460-
AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
461-
462-
if (m_UseHost || m_ServerNetworkManager.IsHost)
470+
// When true, we skip everything else (most likely a connection oriented test)
471+
if (!m_BypassConnectionTimeout)
463472
{
464-
// Add the server player instance to all m_ClientSidePlayerNetworkObjects entries
465-
var serverPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
466-
foreach (var playerNetworkObject in serverPlayerClones)
473+
// Wait for all clients to connect
474+
yield return WaitForClientsConnectedOrTimeOut();
475+
476+
AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
477+
478+
if (m_UseHost || m_ServerNetworkManager.IsHost)
467479
{
468-
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
480+
// Add the server player instance to all m_ClientSidePlayerNetworkObjects entries
481+
var serverPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
482+
foreach (var playerNetworkObject in serverPlayerClones)
469483
{
470-
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
484+
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
485+
{
486+
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
487+
}
488+
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
471489
}
472-
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
473490
}
474-
}
475491

476-
ClientNetworkManagerPostStartInit();
492+
ClientNetworkManagerPostStartInit();
477493

478-
// Notification that at this time the server and client(s) are instantiated,
479-
// started, and connected on both sides.
480-
yield return OnServerAndClientsConnected();
494+
// Notification that at this time the server and client(s) are instantiated,
495+
// started, and connected on both sides.
496+
yield return OnServerAndClientsConnected();
481497

482-
VerboseDebug($"Exiting {nameof(StartServerAndClients)}");
498+
VerboseDebug($"Exiting {nameof(StartServerAndClients)}");
499+
}
483500
}
484501
}
485502

0 commit comments

Comments
 (0)