Skip to content

Commit 906d0b8

Browse files
feat!: Handle relay allocation timeouts [MTT-3706] (#1994)
1 parent 4e218bd commit 906d0b8

File tree

5 files changed

+158
-96
lines changed

5 files changed

+158
-96
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

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

1111
### Added
1212

13+
- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994)
14+
- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994)
1315
- Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969)
1416
- Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950)
1517

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,16 @@ public IReadOnlyList<ulong> ConnectedClientsIds
361361
/// </summary>
362362
public event Action OnServerStarted = null;
363363

364+
/// <summary>
365+
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
366+
/// </summary>
367+
/// <remarks>
368+
/// A failure of the transport is always followed by the <see cref="NetworkManager"/> shutting down. Recovering
369+
/// from a transport failure would normally entail reconfiguring the transport (e.g. re-authenticating, or
370+
/// recreating a new service allocation depending on the transport) and restarting the client/server/host.
371+
/// </remarks>
372+
public event Action OnTransportFailure = null;
373+
364374
/// <summary>
365375
/// Connection Approval Response
366376
/// </summary>
@@ -980,6 +990,7 @@ public bool StartServer()
980990
else
981991
{
982992
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
993+
OnTransportFailure?.Invoke();
983994
Shutdown();
984995
}
985996

@@ -1007,6 +1018,7 @@ public bool StartClient()
10071018
if (!NetworkConfig.NetworkTransport.StartClient())
10081019
{
10091020
Debug.LogError($"Client is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
1021+
OnTransportFailure?.Invoke();
10101022
Shutdown();
10111023
return false;
10121024
}
@@ -1039,6 +1051,7 @@ public bool StartHost()
10391051
if (!NetworkConfig.NetworkTransport.StartServer())
10401052
{
10411053
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
1054+
OnTransportFailure?.Invoke();
10421055
Shutdown();
10431056
return false;
10441057
}
@@ -1675,6 +1688,12 @@ private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, A
16751688
s_TransportDisconnect.End();
16761689
#endif
16771690
break;
1691+
1692+
case NetworkEvent.TransportFailure:
1693+
Debug.LogError($"Shutting down due to network transport failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
1694+
OnTransportFailure?.Invoke();
1695+
Shutdown(true);
1696+
break;
16781697
}
16791698
}
16801699

com.unity.netcode.gameobjects/Runtime/Transports/NetworkEvent.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ public enum NetworkEvent
2020
/// </summary>
2121
Disconnect,
2222

23+
/// <summary>
24+
/// Transport has encountered an unrecoverable failure
25+
/// </summary>
26+
TransportFailure,
27+
2328
/// <summary>
2429
/// No new event
2530
/// </summary>

com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,15 @@ private void Update()
722722

723723
m_Driver.ScheduleUpdate().Complete();
724724

725+
if (m_ProtocolType == ProtocolType.RelayUnityTransport && m_Driver.GetRelayConnectionStatus() == RelayConnectionStatus.AllocationInvalid)
726+
{
727+
Debug.LogError("Transport failure! Relay allocation needs to be recreated, and NetworkManager restarted. " +
728+
"Use NetworkManager.OnTransportFailure to be notified of such events programmatically.");
729+
730+
InvokeOnTransportEvent(NetcodeNetworkEvent.TransportFailure, 0, default, Time.realtimeSinceStartup);
731+
return;
732+
}
733+
725734
while (AcceptConnection() && m_Driver.IsCreated)
726735
{
727736
;

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

Lines changed: 123 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -3,131 +3,158 @@
33
using UnityEngine;
44
using UnityEngine.TestTools;
55
using NUnit.Framework;
6-
using Unity.Netcode.TestHelpers.Runtime;
7-
using Object = UnityEngine.Object;
86

97
namespace Unity.Netcode.RuntimeTests
108
{
11-
[TestFixture(HostOrServer.Host)]
12-
[TestFixture(HostOrServer.Server)]
13-
public class NetworkManagerTransportTests : NetcodeIntegrationTest
9+
public class NetworkManagerTransportTests
1410
{
15-
protected override int NumberOfClients => 1;
11+
[Test]
12+
public void ClientDoesNotStartWhenTransportFails()
13+
{
14+
bool callbackInvoked = false;
15+
Action onTransportFailure = () => { callbackInvoked = true; };
16+
17+
var manager = new GameObject().AddComponent<NetworkManager>();
18+
manager.OnTransportFailure += onTransportFailure;
19+
20+
var transport = manager.gameObject.AddComponent<FailedTransport>();
21+
transport.FailOnStart = true;
1622

17-
private bool m_CanStartServerAndClients = false;
23+
manager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport };
1824

19-
public NetworkManagerTransportTests(HostOrServer hostOrServer) : base(hostOrServer) { }
25+
LogAssert.Expect(LogType.Error, $"Client is shutting down due to network transport start failure of {transport.GetType().Name}!");
2026

21-
protected override IEnumerator OnSetup()
27+
Assert.False(manager.StartClient());
28+
Assert.False(manager.IsListening);
29+
Assert.False(manager.IsConnectedClient);
30+
31+
Assert.True(callbackInvoked);
32+
}
33+
34+
[Test]
35+
public void HostDoesNotStartWhenTransportFails()
2236
{
23-
m_CanStartServerAndClients = false;
24-
return base.OnSetup();
37+
bool callbackInvoked = false;
38+
Action onTransportFailure = () => { callbackInvoked = true; };
39+
40+
var manager = new GameObject().AddComponent<NetworkManager>();
41+
manager.OnTransportFailure += onTransportFailure;
42+
43+
var transport = manager.gameObject.AddComponent<FailedTransport>();
44+
transport.FailOnStart = true;
45+
46+
manager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport };
47+
48+
LogAssert.Expect(LogType.Error, $"Server is shutting down due to network transport start failure of {transport.GetType().Name}!");
49+
50+
Assert.False(manager.StartHost());
51+
Assert.False(manager.IsListening);
52+
53+
Assert.True(callbackInvoked);
2554
}
2655

27-
protected override bool CanStartServerAndClients()
56+
[Test]
57+
public void ServerDoesNotStartWhenTransportFails()
2858
{
29-
return m_CanStartServerAndClients;
59+
bool callbackInvoked = false;
60+
Action onTransportFailure = () => { callbackInvoked = true; };
61+
62+
var manager = new GameObject().AddComponent<NetworkManager>();
63+
manager.OnTransportFailure += onTransportFailure;
64+
65+
var transport = manager.gameObject.AddComponent<FailedTransport>();
66+
transport.FailOnStart = true;
67+
68+
manager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport };
69+
70+
LogAssert.Expect(LogType.Error, $"Server is shutting down due to network transport start failure of {transport.GetType().Name}!");
71+
72+
Assert.False(manager.StartServer());
73+
Assert.False(manager.IsListening);
74+
75+
Assert.True(callbackInvoked);
76+
}
77+
78+
[UnityTest]
79+
public IEnumerator ShutsDownWhenTransportFails()
80+
{
81+
bool callbackInvoked = false;
82+
Action onTransportFailure = () => { callbackInvoked = true; };
83+
84+
var manager = new GameObject().AddComponent<NetworkManager>();
85+
manager.OnTransportFailure += onTransportFailure;
86+
87+
var transport = manager.gameObject.AddComponent<FailedTransport>();
88+
transport.FailOnNextPoll = true;
89+
90+
manager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport };
91+
92+
Assert.True(manager.StartServer());
93+
Assert.True(manager.IsListening);
94+
95+
LogAssert.Expect(LogType.Error, $"Shutting down due to network transport failure of {transport.GetType().Name}!");
96+
97+
// Need two updates to actually shut down. First one to see the transport failing, which
98+
// marks the NetworkManager as shutting down. Second one where actual shutdown occurs.
99+
yield return null;
100+
yield return null;
101+
102+
Assert.False(manager.IsListening);
103+
Assert.True(callbackInvoked);
30104
}
31105

32106
/// <summary>
33-
/// Validate that if the NetworkTransport fails to start the NetworkManager
34-
/// will not continue the startup process and will shut itself down.
107+
/// Does nothing but simulate a transport that can fail at startup and/or when polling events.
35108
/// </summary>
36-
/// <param name="testClient">if true it will test the client side</param>
37-
[UnityTest]
38-
public IEnumerator DoesNotStartWhenTransportFails([Values] bool testClient)
109+
public class FailedTransport : TestingNetworkTransport
39110
{
40-
// The error message we should expect
41-
var messageToCheck = "";
42-
if (!testClient)
43-
{
44-
Object.DestroyImmediate(m_ServerNetworkManager.NetworkConfig.NetworkTransport);
45-
m_ServerNetworkManager.NetworkConfig.NetworkTransport = m_ServerNetworkManager.gameObject.AddComponent<FailedTransport>();
46-
m_ServerNetworkManager.NetworkConfig.NetworkTransport.Initialize(m_ServerNetworkManager);
47-
// The error message we should expect
48-
messageToCheck = $"Server is shutting down due to network transport start failure of {m_ServerNetworkManager.NetworkConfig.NetworkTransport.GetType().Name}!";
49-
}
50-
else
111+
public bool FailOnStart = false;
112+
public bool FailOnNextPoll = false;
113+
114+
public override bool StartClient() => !FailOnStart;
115+
116+
public override bool StartServer() => !FailOnStart;
117+
118+
public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment<byte> payload, out float receiveTime)
51119
{
52-
foreach (var client in m_ClientNetworkManagers)
120+
clientId = 0;
121+
payload = new ArraySegment<byte>();
122+
receiveTime = 0;
123+
124+
if (FailOnNextPoll)
53125
{
54-
Object.DestroyImmediate(client.NetworkConfig.NetworkTransport);
55-
client.NetworkConfig.NetworkTransport = client.gameObject.AddComponent<FailedTransport>();
56-
client.NetworkConfig.NetworkTransport.Initialize(m_ServerNetworkManager);
126+
FailOnNextPoll = false;
127+
return NetworkEvent.TransportFailure;
57128
}
58-
// The error message we should expect
59-
messageToCheck = $"Client is shutting down due to network transport start failure of {m_ClientNetworkManagers[0].NetworkConfig.NetworkTransport.GetType().Name}!";
60-
}
61-
62-
// Trap for the nested NetworkManager exception
63-
LogAssert.Expect(LogType.Error, messageToCheck);
64-
m_CanStartServerAndClients = true;
65-
// Due to other errors, we must not send clients if testing the server-host side
66-
// We can test both server and client(s) when testing client-side only
67-
if (testClient)
68-
{
69-
NetcodeIntegrationTestHelpers.Start(m_UseHost, m_ServerNetworkManager, m_ClientNetworkManagers);
70-
yield return s_DefaultWaitForTick;
71-
foreach (var client in m_ClientNetworkManagers)
129+
else
72130
{
73-
Assert.False(client.IsListening);
74-
Assert.False(client.IsConnectedClient);
131+
return NetworkEvent.Nothing;
75132
}
76133
}
77-
else
134+
135+
public override ulong ServerClientId => 0;
136+
137+
public override void Send(ulong clientId, ArraySegment<byte> payload, NetworkDelivery networkDelivery)
78138
{
79-
NetcodeIntegrationTestHelpers.Start(m_UseHost, m_ServerNetworkManager, new NetworkManager[] { });
80-
yield return s_DefaultWaitForTick;
81-
Assert.False(m_ServerNetworkManager.IsListening);
82139
}
83-
}
84-
}
85140

86-
/// <summary>
87-
/// Does nothing but simulate a transport that failed to start
88-
/// </summary>
89-
public class FailedTransport : TestingNetworkTransport
90-
{
91-
public override void Shutdown()
92-
{
93-
}
141+
public override void Initialize(NetworkManager networkManager = null)
142+
{
143+
}
94144

95-
public override ulong ServerClientId => 0;
145+
public override void Shutdown()
146+
{
147+
}
96148

97-
public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment<byte> payload, out float receiveTime)
98-
{
99-
clientId = 0;
100-
payload = new ArraySegment<byte>();
101-
receiveTime = 0;
102-
return NetworkEvent.Nothing;
103-
}
104-
public override bool StartClient()
105-
{
106-
// Simulate failure, always return false
107-
return false;
108-
}
109-
public override bool StartServer()
110-
{
111-
// Simulate failure, always return false
112-
return false;
113-
}
114-
public override void Send(ulong clientId, ArraySegment<byte> payload, NetworkDelivery networkDelivery)
115-
{
116-
}
149+
public override ulong GetCurrentRtt(ulong clientId) => 0;
117150

118-
public override void DisconnectRemoteClient(ulong clientId)
119-
{
120-
}
151+
public override void DisconnectRemoteClient(ulong clientId)
152+
{
153+
}
121154

122-
public override void Initialize(NetworkManager networkManager = null)
123-
{
124-
}
125-
public override ulong GetCurrentRtt(ulong clientId)
126-
{
127-
return 0;
128-
}
129-
public override void DisconnectLocalClient()
130-
{
155+
public override void DisconnectLocalClient()
156+
{
157+
}
131158
}
132159
}
133160
}

0 commit comments

Comments
 (0)