From ff3075a486e4c2c45117d7e13a1d74b1a411b2fa Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 27 Jan 2026 19:36:38 -0600 Subject: [PATCH 01/10] fix Fixes the issue where NetworkManager would not clean up if an exception was thrown while it was starting. --- .../Runtime/Core/NetworkManager.cs | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 66f1a67e3b..898a8973bf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1323,7 +1323,17 @@ public bool StartServer() } ConnectionManager.LocalClient.ClientId = ServerClientId; - Initialize(true); + try + { + Initialize(true); + } + catch (Exception ex) + { + Debug.LogException(ex); + // Always shutdown to assure everything is cleaned up + ShutdownInternal(); + return false; + } try { @@ -1342,11 +1352,13 @@ public bool StartServer() ConnectionManager.TransportFailureEventHandler(true); } - catch (Exception) + catch (Exception ex) { + Debug.LogException(ex); + // Always shutdown to assure everything is cleaned up + ShutdownInternal(); ConnectionManager.LocalClient.SetRole(false, false); IsListening = false; - throw; } return IsListening; @@ -1373,7 +1385,16 @@ public bool StartClient() return false; } - Initialize(false); + try + { + Initialize(false); + } + catch (Exception ex) + { + Debug.LogException(ex); + ShutdownInternal(); + return false; + } try { @@ -1419,7 +1440,18 @@ public bool StartHost() return false; } - Initialize(true); + try + { + Initialize(true); + } + catch (Exception ex) + { + Debug.LogException(ex); + // Always shutdown to assure everything is cleaned up + ShutdownInternal(); + return false; + } + try { IsListening = NetworkConfig.NetworkTransport.StartServer(); @@ -1437,6 +1469,8 @@ public bool StartHost() catch (Exception ex) { Debug.LogException(ex); + // Always shutdown to assure everything is cleaned up + ShutdownInternal(); ConnectionManager.LocalClient.SetRole(false, false); IsListening = false; } From b3ed5c4bb32d78ea8db1c29aee77f5257e01acc6 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 30 Jan 2026 13:30:40 -0500 Subject: [PATCH 02/10] Move all connection tests into the Connection folder --- .../Tests/Runtime/{ => Connection}/ClientApprovalDenied.cs | 0 .../Tests/Runtime/{ => Connection}/ClientApprovalDenied.cs.meta | 0 .../Tests/Runtime/{ => Connection}/ClientOnlyConnectionTests.cs | 0 .../Runtime/{ => Connection}/ClientOnlyConnectionTests.cs.meta | 0 .../Tests/Runtime/{ => Connection}/DisconnectTests.cs | 0 .../Tests/Runtime/{ => Connection}/DisconnectTests.cs.meta | 0 .../Tests/Runtime/{ => Connection}/InvalidConnectionEventsTest.cs | 0 .../Runtime/{ => Connection}/InvalidConnectionEventsTest.cs.meta | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/ClientApprovalDenied.cs (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/ClientApprovalDenied.cs.meta (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/ClientOnlyConnectionTests.cs (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/ClientOnlyConnectionTests.cs.meta (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/DisconnectTests.cs (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/DisconnectTests.cs.meta (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/InvalidConnectionEventsTest.cs (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/InvalidConnectionEventsTest.cs.meta (100%) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientApprovalDenied.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientApprovalDenied.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientApprovalDenied.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientApprovalDenied.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientOnlyConnectionTests.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientOnlyConnectionTests.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientOnlyConnectionTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientOnlyConnectionTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/DisconnectTests.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/DisconnectTests.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/DisconnectTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/DisconnectTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/InvalidConnectionEventsTest.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/InvalidConnectionEventsTest.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/InvalidConnectionEventsTest.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/InvalidConnectionEventsTest.cs.meta From a8192656259a43cb4170953f4d89e6fe8d160fb0 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 30 Jan 2026 13:31:45 -0500 Subject: [PATCH 03/10] Catch any exceptions in PrefabHandler.Instantiate --- .../Runtime/Spawning/NetworkPrefabHandler.cs | 20 +++++++- .../NetworkObjectDestroyTests.cs | 2 +- .../Prefabs/NetworkPrefabHandlerTests.cs | 49 +++++++++++++++---- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index 7b8632d3c3..366578c414 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -287,7 +287,15 @@ internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulo { if (m_PrefabAssetToPrefabHandlerWithData.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler)) { - networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation, instantiationData); + try + { + networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation, instantiationData); + } + catch(Exception ex) + { + Debug.LogException(ex); + return null; + } } else { @@ -297,7 +305,15 @@ internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulo } else if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler)) { - networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation); + try + { + networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation); + } + catch(Exception ex) + { + Debug.LogException(ex); + return null; + } } // Now we must make sure this alternate PrefabAsset spawned in place of the prefab asset with the networkPrefabAssetHash (GlobalObjectIdHash) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 081a141bf6..9f2baa4113 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests /// /// Tests calling destroy on spawned / unspawned s. Expected behavior: /// - Server or client destroy on unspawned => Object gets destroyed, no exceptions - /// - Server destroy spawned => Object gets destroyed and despawned/destroyed on all clients. Server does not run . Client runs it. + /// - Server destroy spawned => Object gets destroyed and despawned/destroyed on all clients. Server does not run . Client runs it. /// - Client destroy spawned => throw exception. /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs index 34b98001ca..4e11adbd3a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { @@ -93,11 +94,11 @@ public void NetworkConfigInvalidNetworkPrefabTest() private const string k_PrefabObjectName = "NetworkPrefabHandlerTestObject"; [Test] - public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) + public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType) { var networkConfig = new NetworkConfig() { - NetworkTopology = distributedAuthority ? NetworkTopologyTypes.DistributedAuthority : NetworkTopologyTypes.ClientServer, + NetworkTopology = topologyType, }; Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _, networkConfig: networkConfig)); @@ -107,13 +108,13 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) NetworkObject baseObject = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; var networkPrefabHandler = new NetworkPrefabHandler(); - var networkPrefaInstanceHandler = new NetworkPrefaInstanceHandler(baseObject); + var networkPrefabInstanceHandler = new NetworkPrefabInstanceHandler(baseObject); var prefabPosition = new Vector3(1.0f, 5.0f, 3.0f); var prefabRotation = new Quaternion(1.0f, 0.5f, 0.4f, 0.1f); //Register via GameObject - var gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.gameObject, networkPrefaInstanceHandler); + var gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.gameObject, networkPrefabInstanceHandler); //Test result of registering via GameObject reference Assert.True(gameObjectRegistered); @@ -134,7 +135,7 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler //Register via NetworkObject - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefaInstanceHandler); + gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefabInstanceHandler); //Test result of registering via NetworkObject reference Assert.True(gameObjectRegistered); @@ -159,7 +160,7 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler //Register via GlobalObjectIdHash - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefaInstanceHandler); + gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefabInstanceHandler); //Test result of registering via GlobalObjectIdHash reference Assert.True(gameObjectRegistered); @@ -183,7 +184,21 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler - Assert.False(networkPrefaInstanceHandler.StillHasInstances()); + // Register a handler that throws an exception + var networkPrefabExceptionThrower = new NetworkPrefabExceptionThrower(); + gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefabExceptionThrower); + //Test result of registering exception handler + Assert.True(gameObjectRegistered); + + LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); + spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + + // No object should have been spawned, but test should have continued + Assert.Null(spawnedObject); + + networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + + Assert.False(networkPrefabInstanceHandler.StillHasInstances()); } [SetUp] @@ -216,7 +231,7 @@ public void TearDown() /// /// The Prefab instance handler to use for this test /// - internal class NetworkPrefaInstanceHandler : INetworkPrefabInstanceHandler + internal class NetworkPrefabInstanceHandler : INetworkPrefabInstanceHandler { private NetworkObject m_NetworkObject; @@ -243,10 +258,26 @@ public bool StillHasInstances() return (m_Instances.Count > 0); } - public NetworkPrefaInstanceHandler(NetworkObject networkObject) + public NetworkPrefabInstanceHandler(NetworkObject networkObject) { m_NetworkObject = networkObject; m_Instances = new List(); } } + + /// + /// Causes an exception during client connection + /// + internal class NetworkPrefabExceptionThrower : INetworkPrefabInstanceHandler + { + public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) + { + throw new Exception("exception while instantiating"); + } + + public void Destroy(NetworkObject networkObject) + { + UnityEngine.Object.Destroy(networkObject.gameObject); + } + } } From 1e7a70ce1ede8c0d8c758230d56c758ef9bf27ee Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 30 Jan 2026 13:33:23 -0500 Subject: [PATCH 04/10] Add tests for exceptions thrown during startup --- .../Runtime/Core/NetworkManager.cs | 4 +- .../Connection/StartupExceptionTests.cs | 146 ++++++++++++++++++ .../Connection/StartupExceptionTests.cs.meta | 3 + .../NetcodeIntegrationTestHelpers.cs | 21 ++- 4 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 898a8973bf..e05548e697 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1357,7 +1357,6 @@ public bool StartServer() Debug.LogException(ex); // Always shutdown to assure everything is cleaned up ShutdownInternal(); - ConnectionManager.LocalClient.SetRole(false, false); IsListening = false; } @@ -1412,7 +1411,7 @@ public bool StartClient() catch (Exception ex) { Debug.LogException(ex); - ConnectionManager.LocalClient.SetRole(false, false); + ShutdownInternal(); IsListening = false; } @@ -1471,7 +1470,6 @@ public bool StartHost() Debug.LogException(ex); // Always shutdown to assure everything is cleaned up ShutdownInternal(); - ConnectionManager.LocalClient.SetRole(false, false); IsListening = false; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs new file mode 100644 index 0000000000..3c63784923 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.Transports.UTP; +using UnityEngine; +using UnityEngine.TestTools; +using Object = System.Object; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(StartType.Server)] + [TestFixture(StartType.Host)] + [TestFixture(StartType.Client)] + internal class StartupExceptionTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + public enum StartType + { + Server, + Host, + Client + } + + private StartType m_StartType; + public StartupExceptionTests(StartType startType) : base(startType == StartType.Server ? HostOrServer.Server : HostOrServer.Host) + { + m_StartType = startType; + } + + private const string k_ExceptionText = "This is a test exception"; + + private void ThrowExceptionAction() + { + throw new Exception(k_ExceptionText); + } + + private SessionConfig SessionConfigExceptionThrower() + { + throw new Exception(k_ExceptionText); + } + + private void OnPeerConnectedException(NetworkManager networkManager, ConnectionEventData connectionEventData) + { + if (connectionEventData.EventType == ConnectionEvent.PeerConnected) + { + ThrowExceptionAction(); + } + } + + private void OnClientConnectedException(NetworkManager networkManager, ConnectionEventData connectionEventData) + { + if (connectionEventData.EventType == ConnectionEvent.ClientConnected) + { + ThrowExceptionAction(); + } + } + + + [UnityTest] + public IEnumerator VerifyNetworkManagerHandlesExceptionDuringStart() + { + var startType = m_StartType; + NetworkManager toTest; + if (startType == StartType.Client) + { + toTest = CreateNewClient(); + } + else + { + toTest = GetAuthorityNetworkManager(); + yield return StopOneClient(toTest); + } + + var transport = toTest.NetworkConfig.NetworkTransport as UnityTransport; + Assert.That(transport, Is.Not.Null, "Transport should not be null"); + + var isListening = true; + + /* + * Test exception being thrown during NetworkManager.Initialize() + */ + + // It's not possible to throw an exception during server initialization + if (startType != StartType.Server) + { + // OnSessionConfig is called within Initialize only in DAMode + toTest.NetworkConfig.NetworkTopology = NetworkTopologyTypes.DistributedAuthority; + + toTest.OnGetSessionConfig += SessionConfigExceptionThrower; + + LogAssert.Expect(LogType.Exception, $"Exception: {k_ExceptionText}"); + isListening = startType == StartType.Host ? toTest.StartHost() : toTest.StartClient(); + + Assert.That(isListening, Is.False, "Should not have started after exception during Initialize()"); + Assert.That(transport.GetNetworkDriver().IsCreated, Is.False, "NetworkDriver should not be created."); + + toTest.OnGetSessionConfig -= SessionConfigExceptionThrower; + toTest.NetworkConfig.NetworkTopology = NetworkTopologyTypes.ClientServer; + } + + /* + * Test exception being thrown after NetworkTransport.StartClient() + */ + + toTest.OnClientStarted += ThrowExceptionAction; + toTest.OnServerStarted += ThrowExceptionAction; + + LogAssert.Expect(LogType.Exception, $"Exception: {k_ExceptionText}"); + isListening = startType switch + { + StartType.Server => toTest.StartServer(), + StartType.Host => toTest.StartHost(), + StartType.Client => toTest.StartClient(), + _ => true + }; + + Assert.That(isListening, Is.False, "Should not have started after exception during startup"); + Assert.That(transport.GetNetworkDriver().IsCreated, Is.False, "NetworkDriver should not be created."); + + toTest.OnClientStarted -= ThrowExceptionAction; + toTest.OnServerStarted -= ThrowExceptionAction; + + if (startType == StartType.Client) + { + // Start the client fully to ensure startup still works with no exceptions + yield return StartClient(toTest); + + Assert.That(toTest.IsListening, Is.True, "Client failed to start"); + Assert.That(transport.GetNetworkDriver().IsCreated, Is.True, "NetworkDriver should be created."); + } + else + { + var isHost = startType == StartType.Host; + NetcodeIntegrationTestHelpers.StartServer(isHost, toTest); + var hostOrServer = isHost ? "Host" : "Server"; + Assert.That(toTest.IsListening, Is.True, $"{hostOrServer} failed to start"); + Assert.That(transport.GetNetworkDriver().IsCreated, Is.True, "NetworkDriver should be created."); + + yield return CreateAndStartNewClient(); + } + } + + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs.meta new file mode 100644 index 0000000000..753c875f32 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 258048582a004eef90cf9e702241ba6b +timeCreated: 1769722148 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs index d09d1da9b6..82e1a7075f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs @@ -571,17 +571,26 @@ public static bool Start(bool host, NetworkManager server, NetworkManager[] clie } s_IsStarted = true; + return StartInternal(host, server, clients, callback, startServer); + } + + internal static bool StartServer(bool host, NetworkManager server) + { + return StartInternal(host, server, new NetworkManager[]{}); + } + + + private static bool StartInternal(bool host, NetworkManager server, NetworkManager[] clients, BeforeClientStartCallback callback = null, bool startServer = true) + { s_ClientCount = clients.Length; var hooks = (MultiInstanceHooks)null; if (startServer) { - if (host) - { - server.StartHost(); - } - else + var isListening = host ? server.StartHost() : server.StartServer(); + + if (!isListening) { - server.StartServer(); + return false; } hooks = new MultiInstanceHooks(); From 811c22cbf5be603f575f3da3ab6a51e97b2d62ce Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 30 Jan 2026 13:38:07 -0500 Subject: [PATCH 05/10] Update CHANGELOG --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 38656d18cd..f8d79f12c8 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added better exception handling around the startup sequence. (#3864) - Added stricter checks on `InSpawned` within `NetworkObject`. (#3831) - Added a new `InvalidOperation` status to `OwnershipRequestStatus`. (#3831) From 51dd9bec5539a2497c2386e497247fa26bff0688 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 30 Jan 2026 13:38:33 -0500 Subject: [PATCH 06/10] Fix code formatting and remove unused code --- .../Runtime/Spawning/NetworkPrefabHandler.cs | 4 ++-- .../Connection/StartupExceptionTests.cs | 20 +------------------ .../NetcodeIntegrationTestHelpers.cs | 2 +- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index 366578c414..c8aab939f4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -291,7 +291,7 @@ internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulo { networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation, instantiationData); } - catch(Exception ex) + catch (Exception ex) { Debug.LogException(ex); return null; @@ -309,7 +309,7 @@ internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulo { networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation); } - catch(Exception ex) + catch (Exception ex) { Debug.LogException(ex); return null; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs index 3c63784923..5280e40a7e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs @@ -5,7 +5,6 @@ using Unity.Netcode.Transports.UTP; using UnityEngine; using UnityEngine.TestTools; -using Object = System.Object; namespace Unity.Netcode.RuntimeTests { @@ -41,23 +40,6 @@ private SessionConfig SessionConfigExceptionThrower() throw new Exception(k_ExceptionText); } - private void OnPeerConnectedException(NetworkManager networkManager, ConnectionEventData connectionEventData) - { - if (connectionEventData.EventType == ConnectionEvent.PeerConnected) - { - ThrowExceptionAction(); - } - } - - private void OnClientConnectedException(NetworkManager networkManager, ConnectionEventData connectionEventData) - { - if (connectionEventData.EventType == ConnectionEvent.ClientConnected) - { - ThrowExceptionAction(); - } - } - - [UnityTest] public IEnumerator VerifyNetworkManagerHandlesExceptionDuringStart() { @@ -124,7 +106,7 @@ public IEnumerator VerifyNetworkManagerHandlesExceptionDuringStart() if (startType == StartType.Client) { - // Start the client fully to ensure startup still works with no exceptions + // Start the client fully to ensure startup still works with no exceptions yield return StartClient(toTest); Assert.That(toTest.IsListening, Is.True, "Client failed to start"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs index 82e1a7075f..32a0f734d7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs @@ -576,7 +576,7 @@ public static bool Start(bool host, NetworkManager server, NetworkManager[] clie internal static bool StartServer(bool host, NetworkManager server) { - return StartInternal(host, server, new NetworkManager[]{}); + return StartInternal(host, server, new NetworkManager[] { }); } From ff2f1a884c389988657ff3c048a2410e1d46b2cb Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 30 Jan 2026 14:14:05 -0500 Subject: [PATCH 07/10] Move more connection tests --- .../Tests/Runtime/{ => Connection}/ConnectionApproval.cs | 0 .../Tests/Runtime/{ => Connection}/ConnectionApproval.cs.meta | 0 .../Runtime/{ => Connection}/ConnectionApprovalTimeoutTests.cs | 0 .../{ => Connection}/ConnectionApprovalTimeoutTests.cs.meta | 0 .../Tests/Runtime/{Connection => }/StartupExceptionTests.cs | 2 ++ .../Runtime/{Connection => }/StartupExceptionTests.cs.meta | 0 6 files changed, 2 insertions(+) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/ConnectionApproval.cs (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/ConnectionApproval.cs.meta (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/ConnectionApprovalTimeoutTests.cs (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Connection}/ConnectionApprovalTimeoutTests.cs.meta (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{Connection => }/StartupExceptionTests.cs (95%) rename com.unity.netcode.gameobjects/Tests/Runtime/{Connection => }/StartupExceptionTests.cs.meta (100%) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApprovalTimeoutTests.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApprovalTimeoutTests.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApprovalTimeoutTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApprovalTimeoutTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/StartupExceptionTests.cs similarity index 95% rename from com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/StartupExceptionTests.cs index 5280e40a7e..5addd31712 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/StartupExceptionTests.cs @@ -100,6 +100,8 @@ public IEnumerator VerifyNetworkManagerHandlesExceptionDuringStart() Assert.That(isListening, Is.False, "Should not have started after exception during startup"); Assert.That(transport.GetNetworkDriver().IsCreated, Is.False, "NetworkDriver should not be created."); + Assert.False(toTest.IsServer, "IsServer should be false when NetworkManager failed to start"); + Assert.False(toTest.IsClient, "IsClient should be false when NetworkManager failed to start"); toTest.OnClientStarted -= ThrowExceptionAction; toTest.OnServerStarted -= ThrowExceptionAction; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/StartupExceptionTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/Connection/StartupExceptionTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/StartupExceptionTests.cs.meta From 089068987981e6d637fba2988b94b1824c5f86c9 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 30 Jan 2026 14:14:21 -0500 Subject: [PATCH 08/10] Fix tests --- .../Tests/Editor/Transports/UnityTransportTests.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs index 594467efc2..cfab238370 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs @@ -154,21 +154,21 @@ public void UnityTransport_StartServerWithoutAddresses() [Test] public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] string cert, [Values("", null)] string secret) { - var supportingGO = new GameObject(); + var supportingGo = new GameObject(); try { - var networkManager = supportingGO.AddComponent(); // NM is required for UTP to work with certificates. + var networkManager = supportingGo.AddComponent(); // NM is required for UTP to work with certificates. networkManager.NetworkConfig = new NetworkConfig(); - UnityTransport transport = supportingGO.AddComponent(); + UnityTransport transport = supportingGo.AddComponent(); networkManager.NetworkConfig.NetworkTransport = transport; - transport.Initialize(); + transport.Initialize(networkManager); transport.SetServerSecrets(serverCertificate: cert, serverPrivateKey: secret); // Use encryption, but don't set certificate and check for exception transport.UseEncryption = true; Assert.Throws(() => { - networkManager.StartServer(); + transport.StartServer(); }); // Make sure StartServer failed Assert.False(transport.GetNetworkDriver().IsCreated); @@ -177,9 +177,9 @@ public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] st } finally { - if (supportingGO != null) + if (supportingGo != null) { - Object.DestroyImmediate(supportingGO); + Object.DestroyImmediate(supportingGo); } } } From ef44dc2658426fe487581406e1bbfeaf3671cf7f Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 30 Jan 2026 14:14:59 -0500 Subject: [PATCH 09/10] Update CHANGELOG --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f8d79f12c8..b3bd320d69 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,7 +10,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added -- Added better exception handling around the startup sequence. (#3864) - Added stricter checks on `InSpawned` within `NetworkObject`. (#3831) - Added a new `InvalidOperation` status to `OwnershipRequestStatus`. (#3831) @@ -29,6 +28,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NetworkManager` was not cleaning itself up if an exception was thrown while starting. (#3864) - Fixed memory leak in `NetworkAnimator` on clients where `RpcTarget` groups were not being properly disposed due to incorrect type casting of `ProxyRpcTargetGroup` to `RpcTargetGroup`. - Fixed issue when using a client-server topology where a `NetworkList` with owner write permissions was resetting sent time and dirty flags after having been spawned on owning clients that were not the spawn authority. (#3850) - Fixed an integer overflow that occurred when configuring a large disconnect timeout with Unity Transport. (#3810) From 6ad52fcf16f2b6ae7dde78bb78a61e04017ad83f Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 30 Jan 2026 14:17:48 -0500 Subject: [PATCH 10/10] Rename test file --- ...ExceptionTests.cs => NetworkManagerStartExceptionTests.cs} | 4 ++-- ...ests.cs.meta => NetworkManagerStartExceptionTests.cs.meta} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename com.unity.netcode.gameobjects/Tests/Runtime/{StartupExceptionTests.cs => NetworkManagerStartExceptionTests.cs} (95%) rename com.unity.netcode.gameobjects/Tests/Runtime/{StartupExceptionTests.cs.meta => NetworkManagerStartExceptionTests.cs.meta} (100%) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/StartupExceptionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs similarity index 95% rename from com.unity.netcode.gameobjects/Tests/Runtime/StartupExceptionTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs index 5addd31712..e77f3b59b6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/StartupExceptionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(StartType.Server)] [TestFixture(StartType.Host)] [TestFixture(StartType.Client)] - internal class StartupExceptionTests : NetcodeIntegrationTest + internal class NetworkManagerStartExceptionTests : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -23,7 +23,7 @@ public enum StartType } private StartType m_StartType; - public StartupExceptionTests(StartType startType) : base(startType == StartType.Server ? HostOrServer.Server : HostOrServer.Host) + public NetworkManagerStartExceptionTests(StartType startType) : base(startType == StartType.Server ? HostOrServer.Server : HostOrServer.Host) { m_StartType = startType; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/StartupExceptionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/StartupExceptionTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerStartExceptionTests.cs.meta