Pipeline silently dropping messages when window gains focus.

Hi. I have a really simple setup using the ReliableSequencedPipelineStage (2.0.0-pre.2, Unity 2022.2.0b8) where messages are being dropped without any kind of console message (I am aware of the 32 limit but it is no showing up in console, I am not sure if it is the same problem).

The server code sends a uint for every frame and client prints an error for every number missing.

To reproduce:

  • in two different unity editors add the “Menu” script to some game object and play
  • in one editor click “Start server”
  • in a second editor click “Start client”
  • in the first editor click “Start simulation”
    Server will start to send numers to the client, server and client will log those numbers.
  • click the second editor
    When second editor gains focus, after a mini halt, some messages will be skipped and the missing numbers will be logged as errors.
using Unity.Networking.Transport;
using UnityEngine;

public class Menu : MonoBehaviour
{
    private Server server;
    private Client client;

    private bool serverRunning;
    private bool clientRunning;

    private void OnGUI()
    {
        if (!serverRunning && !clientRunning && GUILayout.Button("Start server"))
        {
            server = new Server();
            var endpoint = NetworkEndpoint.AnyIpv4;
            endpoint.Port = 9000;
            server.Initialize(endpoint);
            serverRunning = true;
        }
       
        if (!serverRunning && !clientRunning && GUILayout.Button("Start client"))
        {
            client = new Client();
            client.Initialize(NetworkEndpoint.Parse("127.0.0.1", 9000));
            clientRunning = true;
        }

        if (serverRunning && GUILayout.Button("Start simulation"))
        {
            server.sendTest = true;
        }
    }
   
    void Update()
    {
        if(serverRunning) server.Update();
        if(clientRunning) client.Update();
    }
}
using System;
using Unity.Collections;
using Unity.Networking.Transport;
using UnityEngine;

public struct Server : IDisposable
{
    private NetworkDriver driver;
    private NetworkPipeline pipeline;
    private NativeList<NetworkConnection> connections;
       
    public bool sendTest;
    private uint acc;

    public void Initialize(NetworkEndpoint endpoint)
    {
        Debug.Log($"SERVER: Initializing at " + endpoint.ToString());
           
        var settings = new NetworkSettings();
        settings.WithNetworkConfigParameters(disconnectTimeoutMS: 3600000);
        driver = NetworkDriver.Create(settings);
        pipeline = driver.CreatePipeline(typeof(ReliableSequencedPipelineStage));
       
        if (driver.Bind(endpoint) != 0)
            Debug.Log($"SERVER: Failed to bind to port {endpoint.Port}");
        else
            driver.Listen();

        connections = new NativeList<NetworkConnection>(16, Allocator.Persistent);
    }

    public void Dispose()
    {
        driver.Dispose();
        connections.Dispose();
    }

    public void Update()
    {
        driver.ScheduleUpdate().Complete();

        // CleanUpConnections
        for (int i = connections.Length - 1; i >= 0; i--)
        {
            if (!connections[i].IsCreated)
            {
                connections.RemoveAt(i);
            }
        }

        // AcceptNewConnections
        NetworkConnection newConnection;
        while ((newConnection = driver.Accept()) != default)
        {
            connections.Add(newConnection);
            Debug.Log($"SERVER: Accepted a connection: clientIndex = {connections.Length - 1}");
        }

        // Handle messages
        for (int clientIndex = 0; clientIndex < connections.Length; clientIndex++)
        {
            var connection = connections[clientIndex];
            NetworkEvent.Type cmd;
            while ((cmd = driver.PopEventForConnection(connection, out var reader)) != NetworkEvent.Type.Empty)
            {
                if (cmd == NetworkEvent.Type.Data)
                {
                       
                }
                else if (cmd == NetworkEvent.Type.Disconnect)
                {
                    Debug.Log($"SERVER: Client disconnected from server: clientIndex = {clientIndex}");
                    connections[clientIndex] = default(NetworkConnection);
                }
            }
        }

        if (sendTest)
        {
            acc++;
               
            Debug.Log($"Sending {acc}");
               
            for (int i = 0; i < connections.Length; i++)
            {
                var connection = connections[i];

                driver.BeginSend(pipeline, connection, out var writer);
                writer.WriteUInt(acc);
                driver.EndSend(writer);
            }
        }
    }
}
using System;
using Unity.Networking.Transport;
using UnityEngine;

public struct Client : IDisposable
{
    public NetworkDriver driver;
    public NetworkPipeline pipeline;
    public NetworkConnection connection;

    public uint acc;

    public void Initialize(NetworkEndpoint endPoint)
    {
        Debug.Log($"CLIENT: Connecting to server in {endPoint.ToString()}");

        var settings = new NetworkSettings();
        settings.WithNetworkConfigParameters(disconnectTimeoutMS: 3600000);
        driver = NetworkDriver.Create(settings);
        pipeline = driver.CreatePipeline(typeof(ReliableSequencedPipelineStage));
        connection = driver.Connect(endPoint);
    }

    public void Dispose()
    {
        driver.Dispose();
    }
       
    public void Update()
    {
        driver.ScheduleUpdate().Complete();

        if (!connection.IsCreated)
        {
            Debug.Log("CLIENT: Connection failed");
            return;
        }

        var cmd = default(NetworkEvent.Type);
        while ((cmd = connection.PopEvent(driver, out var reader)) != default)
        {
            switch (cmd)
            {
                case NetworkEvent.Type.Connect:
                {
                    Debug.Log("CLIENT: Connected to server");
                    break;
                }
                case NetworkEvent.Type.Data:
                {
                    var serverAcc = reader.ReadUInt();

                    while (serverAcc > acc + 1)
                    {
                        Debug.LogError($"Missing {++acc}");
                    }

                    Debug.Log($"Receiving {serverAcc}");
                    acc = serverAcc;

                    break;
                }
                case NetworkEvent.Type.Disconnect:
                {
                    Debug.Log("CLIENT: Disconnected from server");
                    connection = default(NetworkConnection);
                    break;
                }
                default: throw new Exception();
            }
        }
    }
}

It is likely that you are hitting the limit of 32 in-flight messages if you are sending a reliable message every frame. This is signaled through return codes, but the example code above doesn’t handle those so the errors appear to be silent.

In this case, I’d guess EndSend is failing with return code Error.StatusCode.NetworkSendQueueFull (value -5). This indicates that the reliable window is full (i.e. the maximum number of packets in flight has been reached). Note that BeginSend can also fail in a similar manner. Please refer to the FAQ for how to deal with this error. The section of the documentation on the reliable pipeline might also be useful.