Skip to content

Commit 9f88aea

Browse files
fix: unified transport and bootstrap memory leaks (#4219)
fix: release unified transport and bootstrap memory between sessions Dispose each connection's send queue when the connection is removed or the transport shuts down, release the safety handle created per received packet, reuse the UnifiedNetcodeTransport component across restarts, and reset the UnifiedBootstrap statics on load so a disabled domain reload does not keep the previous NetworkManager alive.
1 parent 434d0ad commit 9f88aea

3 files changed

Lines changed: 57 additions & 6 deletions

File tree

‎com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ internal class UnifiedBootstrap : ClientServerBootstrap
3131
// creates its own bootstrap, so the worlds created for other NetworkManagers are registered again.
3232
private static readonly List<NetcodeWorld> k_CreatedWorlds = new List<NetcodeWorld>();
3333

34+
#if UNITY_EDITOR
35+
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
36+
private static void ResetStaticsOnLoad()
37+
{
38+
Instance = null;
39+
OnInitialized = null;
40+
Port = 7979;
41+
CurrentNetworkManagerForInitialization = null;
42+
LastCreatedWorld = null;
43+
s_WorldCounter = 0;
44+
k_CreatedWorlds.Clear();
45+
}
46+
#endif
47+
3448
private static void RegisterCreatedWorlds()
3549
{
3650
for (int i = k_CreatedWorlds.Count - 1; i >= 0; i--)

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1266,7 +1266,12 @@ internal void Initialize(bool server)
12661266
NetworkConfig.InitializePrefabs();
12671267
if (NetworkConfig.Prefabs.HasGhostPrefabs)
12681268
{
1269-
NetworkConfig.NetworkTransport = gameObject.AddComponent<UnifiedNetcodeTransport>();
1269+
// Reused so that restarting a session does not add another transport.
1270+
if (!TryGetComponent(out UnifiedNetcodeTransport unifiedTransport))
1271+
{
1272+
unifiedTransport = gameObject.AddComponent<UnifiedNetcodeTransport>();
1273+
}
1274+
NetworkConfig.NetworkTransport = unifiedTransport;
12701275
}
12711276
#endif
12721277
#if UNIFIED_NETCODE
@@ -1400,6 +1405,7 @@ internal void InitializeNetcodeWorld()
14001405
TypeManager.Initialize();
14011406
UnifiedBootstrap.CurrentNetworkManagerForInitialization = this;
14021407
new UnifiedBootstrap().Initialize("Default World");
1408+
UnifiedBootstrap.CurrentNetworkManagerForInitialization = null;
14031409

14041410
if (NetcodeWorld != null && NetcodeWorld.IsCreated)
14051411
{

‎com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs‎

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,16 @@ private class ConnectionInfo
215215

216216
private Dictionary<int, ConnectionInfo> m_Connections;
217217

218+
private bool RemoveConnection(int connectionId)
219+
{
220+
if (!m_Connections.Remove(connectionId, out ConnectionInfo connectionInfo))
221+
{
222+
return false;
223+
}
224+
connectionInfo.SendQueue.Dispose();
225+
return true;
226+
}
227+
218228
internal void DispatchMessage(int connectionId, in FixedBytes1280 buffer)
219229
{
220230
// The connection is removed on disconnect, which can be ahead of its last messages.
@@ -223,7 +233,7 @@ internal void DispatchMessage(int connectionId, in FixedBytes1280 buffer)
223233
return;
224234
}
225235

226-
using var arr = FixedBytes1280.ToNativeArray(buffer);
236+
var arr = FixedBytes1280.ToNativeArray(buffer);
227237
var reader = new DataStreamReader(arr);
228238
if (connectionInfo.ReceiveQueue == null)
229239
{
@@ -233,6 +243,11 @@ internal void DispatchMessage(int connectionId, in FixedBytes1280 buffer)
233243
{
234244
connectionInfo.ReceiveQueue.PushReader(reader);
235245
}
246+
#if ENABLE_UNITY_COLLECTIONS_CHECKS
247+
// Disposing an Allocator.None array does not release its safety handle. The queue has copied the data.
248+
var safety = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(arr);
249+
CollectionHelper.DisposeSafetyHandle(ref safety);
250+
#endif
236251

237252
var message = connectionInfo.ReceiveQueue.PopMessage();
238253
while (message.Count != 0)
@@ -278,6 +293,7 @@ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment<byte
278293

279294
private void OnClientConnectedToServer(Connection connection, NetcodeConnectionEvent connectionEvent)
280295
{
296+
RemoveConnection(connectionEvent.Id.Value);
281297
m_Connections[connectionEvent.Id.Value] = new ConnectionInfo
282298
{
283299
ReceiveQueue = null,
@@ -292,6 +308,7 @@ private void OnClientConnectedToServer(Connection connection, NetcodeConnectionE
292308

293309
private void OnServerNewClientConnection(Connection connection, NetcodeConnectionEvent connectionEvent)
294310
{
311+
RemoveConnection(connectionEvent.Id.Value);
295312
m_Connections[connectionEvent.Id.Value] = new ConnectionInfo
296313
{
297314
ReceiveQueue = null,
@@ -375,13 +392,14 @@ private void OnClientDisconnectFromServer(Connection connection, NetcodeConnecti
375392
GetDisconnectMessageFromNetworkStreamDisconnectReason(connectionEvent.DisconnectReason)
376393
);
377394
// Removed before notifying, so the shutdown this triggers does not notify again from DisconnectLocalClient.
378-
m_Connections.Remove(connectionEvent.Id.Value);
395+
RemoveConnection(connectionEvent.Id.Value);
379396
InvokeOnTransportEvent(NetworkEvent.Disconnect, (ulong)connectionEvent.Id.Value, default, m_RealTimeProvider.RealTimeSinceStartup);
380397
}
381398

382399
private void OnServerClientDisconnected(Connection connection, NetcodeConnectionEvent connectionEvent)
383400
{
384401
InvokeOnTransportEvent(NetworkEvent.Disconnect, (ulong)connectionEvent.Id.Value, default, m_RealTimeProvider.RealTimeSinceStartup);
402+
RemoveConnection(connectionEvent.Id.Value);
385403
}
386404

387405
private void OnClientConnectionEvent(Connection connection, NetcodeConnectionEvent connectionEvent)
@@ -435,14 +453,18 @@ public override bool StartServer()
435453

436454
public override void DisconnectRemoteClient(ulong clientId)
437455
{
438-
m_NetworkManager.NetcodeWorld.DisconnectAClient(m_Connections[(int)clientId].Connection);
439-
m_Connections.Remove((int)clientId);
456+
// The connection is already removed when the client disconnected first.
457+
if (m_Connections.TryGetValue((int)clientId, out ConnectionInfo connectionInfo))
458+
{
459+
m_NetworkManager.NetcodeWorld.DisconnectAClient(connectionInfo.Connection);
460+
RemoveConnection((int)clientId);
461+
}
440462
}
441463

442464
public override void DisconnectLocalClient()
443465
{
444466
// Remove the connection 1st (the world might not be available)
445-
var wasConnected = m_Connections.Remove((int)ServerClientId);
467+
var wasConnected = RemoveConnection((int)ServerClientId);
446468

447469
// TODO-FIX-REVIEW-ME:
448470
// This was causing errors to occur upon shutdown during an integration test.
@@ -495,6 +517,15 @@ public override void Shutdown()
495517
netcodeWorld.OnConnectionEvent -= OnClientConnectionEvent;
496518
netcodeWorld.OnConnectionEvent -= OnServerConnectionEvent;
497519
}
520+
521+
if (m_Connections != null)
522+
{
523+
foreach (var connectionInfo in m_Connections.Values)
524+
{
525+
connectionInfo.SendQueue.Dispose();
526+
}
527+
m_Connections.Clear();
528+
}
498529
}
499530
}
500531
}

0 commit comments

Comments
 (0)