FishNet icon indicating copy to clipboard operation
FishNet copied to clipboard

ByteBuffer class finalizer generates exceptions on WebGL

Open MisterFOnGitHub opened this issue 3 years ago • 2 comments

Stress testing the client on WebGL (by disconnecting 10 other players at once) generates WebGL exceptions related to ByteBuffer objects being returned to the ByteArrayPool through their finalizer. This seems to work on standalone windows but not WebGL. Accessing another managed object in a finalizer is not allowed.

Link to similar issue on unity's site: https://issuetracker.unity3d.com/issues/webgl-crash-inside-gc

Here is the browser stacktrace: WebGL.framework.js:3 ArgumentException: Destination array was not long enough. Check destIndex and length, and the array's lower bounds Parameter name: destinationArray at System.Array.Copy (System.Array sourceArray, System.Int32 sourceIndex, System.Array destinationArray, System.Int32 destinationIndex, System.Int32 length) [0x00000] in <00000000000000000000000000000000>:0 at System.Collections.Generic.Queue1[T].SetCapacity (System.Int32 capacity) [0x00000] in <00000000000000000000000000000000>:0 at System.Collections.Generic.Queue1[T].Enqueue (T item) [0x00000] in <00000000000000000000000000000000>:0 at FishNet.Utility.Performance.ByteArrayPool.Store (System.Byte[] buffer) [0x00000] in <00000000000000000000000000000000>:0 at FishNet.Connection.ByteBuffer.Finalize () [0x00000] in <00000000000000000000000000000000>:0

MisterFOnGitHub avatar Oct 07 '22 06:10 MisterFOnGitHub

I fixed it on my end and confirm that the browser crashes are gone.

I fixed it on my end with this in ByteBuffer class:

public void Dispose() { if (Data != null) ByteArrayPool.Store(Data); Data = null; }

~ByteBuffer()
{
    if (Data != null)
        ByteArrayPool.Store(Data);
}

And this in PacketBundle class: public void Dispose() { for (int i = 0; i < _buffers.Count; i++) _buffers[i].Dispose(); }

And this in networkconnection class: public void Dispose() { foreach (PacketBundle p in _toClientBundles) p.Dispose(); _toClientBundles.Clear(); }

this Dispose gets called in ClientManager.cs, before both Clients.Clear(): foreach (NetworkConnection c in Clients.Values) c.Dispose(); Clients.Clear();

And also before Clients.Remove(args.Id); in OnClientConnectionBroadcast method

MisterFOnGitHub avatar Oct 18 '22 21:10 MisterFOnGitHub

Thanks for the follow-up. I can take a look in the upcoming days now that I've finally got the PredictedObject out.

FirstGearGames avatar Oct 18 '22 22:10 FirstGearGames

I added your information in with some changes. Inside NetworkManager...

        /// <summary>
        /// Clears a client collection after disposing of the NetworkConnections.
        /// </summary>
        /// <param name="clients"></param>
        internal void ClearClientsCollection(Dictionary<int, NetworkConnection> clients)
        {
            foreach (NetworkConnection conn in clients.Values)
                conn.Dispose();

            clients.Clear();
        }

This is called by server and client when clearing collection. Also, I added the asServer check to the NetworkConnection class.

       private void Initialize(NetworkManager nm, int clientId, bool asServer)
        {
            NetworkManager = nm;
            ClientId = clientId;
            //Only the server uses the ping and buffer.
            if (asServer)
            {
                InitializeBuffer();
                InitializePing();
            }
        }

Previously the toClientBuffers were initializing on clients, which is not needed.

These changes will be available in 2.5.6.

FirstGearGames avatar Oct 19 '22 19:10 FirstGearGames