From 2eca5b0dcb54ee9e03019278a720b11b52b52d30 Mon Sep 17 00:00:00 2001 From: Unity Technologies <@unity> Date: Wed, 10 Aug 2022 00:00:00 +0000 Subject: [PATCH] com.unity.transport@1.2.0 ## [1.2.0] - 2022-08-10 ### New features * If using the default network interface, the transport will attempt to transparently recreate the underlying network socket if it fails. This should increase robustness, especially on mobile where the OS might close sockets when an application is sent to the background. ### Changes * A new `NetworkSocketError` value has been added to `Error.StatusCode`. This will be returned through `NetworkDriver.ReceiveErrorCode` when the automatic socket recreation mentioned above has failed (indicating an unrecoverable network failure). ### Fixes * On iOS, communications will restart correctly if the application was in the background. Note that if using Relay, it's still possible for the allocation to have timed out while in the background. Recreation of a new allocation with a new `NetworkDriver` is still required in that scenario. * Fixed a possible stack overflow if the receive queue parameter was configured with a very large value (>10,000). --- CHANGELOG.md | 12 + Runtime/AppForegroundTracker.cs | 38 --- Runtime/AppForegroundTracker.cs.meta | 11 - Runtime/BaselibNetworkInterface.cs | 284 ++++++++++-------- Runtime/DataStream.cs | 12 +- Runtime/NetworkConnection.cs | 5 +- Runtime/NetworkDriver.cs | 2 + Tests/Editor/RelayNetworkDriverTests.cs | 11 + Tests/Runtime/BaselibNetworkInterfaceTests.cs | 76 ++++- Tests/Runtime/ConnectDisconnectTests.cs | 1 + Tests/Runtime/DisconnectTimeoutTests.cs | 1 + Tests/Runtime/SendMessageTests.cs | 1 + ValidationExceptions.json | 4 +- package.json | 9 +- 14 files changed, 268 insertions(+), 199 deletions(-) delete mode 100644 Runtime/AppForegroundTracker.cs delete mode 100644 Runtime/AppForegroundTracker.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 4534dc2..ad17e42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change log +## [1.2.0] - 2022-08-10 + +### New features +* If using the default network interface, the transport will attempt to transparently recreate the underlying network socket if it fails. This should increase robustness, especially on mobile where the OS might close sockets when an application is sent to the background. + +### Changes +* A new `NetworkSocketError` value has been added to `Error.StatusCode`. This will be returned through `NetworkDriver.ReceiveErrorCode` when the automatic socket recreation mentioned above has failed (indicating an unrecoverable network failure). + +### Fixes +* On iOS, communications will restart correctly if the application was in the background. Note that if using Relay, it's still possible for the allocation to have timed out while in the background. Recreation of a new allocation with a new `NetworkDriver` is still required in that scenario. +* Fixed a possible stack overflow if the receive queue parameter was configured with a very large value (>10,000). + ## [1.1.0] - 2022-06-14 ### New features diff --git a/Runtime/AppForegroundTracker.cs b/Runtime/AppForegroundTracker.cs deleted file mode 100644 index 3979baa..0000000 --- a/Runtime/AppForegroundTracker.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Diagnostics; -using Unity.Burst; -using UnityEngine; - -namespace Unity.Networking.Transport.Utilities -{ - /// - /// Tracks the last time the app was brought in the foreground. - /// - internal class AppForegroundTracker - { - private static readonly SharedStatic s_LastForegroundTimestamp = - SharedStatic.GetOrCreate(); - - private class LastForegroundTimestampKey {} - - public static long LastForegroundTimestamp => s_LastForegroundTimestamp.Data; - -#if UNITY_IOS && !DOTS_RUNTIME // See MTT-3702 regarding DOTS Runtime. - [RuntimeInitializeOnLoadMethod] - private static void InitializeTracker() - { - Application.focusChanged += OnFocusChanged; - s_LastForegroundTimestamp.Data = 0; - } -#endif - - internal static void OnFocusChanged(bool focused) - { - if (focused) - { - var stopwatchTime = Stopwatch.GetTimestamp(); - var ts = stopwatchTime / (Stopwatch.Frequency / 1000); - s_LastForegroundTimestamp.Data = ts; - } - } - } -} \ No newline at end of file diff --git a/Runtime/AppForegroundTracker.cs.meta b/Runtime/AppForegroundTracker.cs.meta deleted file mode 100644 index 0da2142..0000000 --- a/Runtime/AppForegroundTracker.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a99855465b0bb744aa9628890fbba483 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/BaselibNetworkInterface.cs b/Runtime/BaselibNetworkInterface.cs index 574026c..60ab9b6 100644 --- a/Runtime/BaselibNetworkInterface.cs +++ b/Runtime/BaselibNetworkInterface.cs @@ -200,12 +200,32 @@ public void ReleaseHandle(int handle) private const int k_defaultTxQueueSize = 64; private const int k_defaultMaximumPayloadSize = 2000; + // Safety value for the maximum number of times we can recreate a socket. Recreating a + // socket so many times would indicate some deeper issue that we won't solve by opening + // new sockets all the time. This also prevents logging endlessly if we get stuck in a + // loop of recreating sockets very frequently. + private const uint k_MaxNumSocketRecreate = 1000; + + // We process the results in batches because if we allocate a big number of results, + // it can cause a stack overflow. + const uint k_RequestsBatchSize = 64; + + internal enum SocketStatus + { + SocketNormal, + SocketNeedsRecreate, + SocketFailed, + } + internal unsafe struct BaselibData { public NetworkSocket m_Socket; + public SocketStatus m_SocketStatus; public Payloads m_PayloadsTx; public NetworkInterfaceEndPoint m_LocalEndpoint; public long m_LastUpdateTime; + public long m_LastSocketRecreateTime; + public uint m_NumSocketRecreate; } [ReadOnly] @@ -340,8 +360,6 @@ public void Dispose() m_Baselib.Dispose(); } - #region ReceiveJob - [BurstCompile] struct FlushSendJob : IJob { @@ -350,64 +368,48 @@ struct FlushSendJob : IJob public NativeArray Baselib; public unsafe void Execute() { - // We process the results in batches because if we allocate a big number of results here, - // it can cause a stack overflow - const uint k_ResultsBufferSize = 64; - const int k_MaxIterations = 500; - var results = stackalloc Binding.Baselib_RegisteredNetwork_CompletionResult[(int)k_ResultsBufferSize]; + var results = stackalloc Binding.Baselib_RegisteredNetwork_CompletionResult[(int)k_RequestsBatchSize]; var error = default(ErrorState); - var pollCount = 0; - var pendingSend = Tx.InUse > 0; - var maxIterations = k_MaxIterations; - while (pendingSend) + // We ensure we never process more than the actual capacity to prevent unexpected deadlocks + for (var sendCount = 0; sendCount < Tx.Capacity; sendCount++) { - // We ensure we never process more than the actual capacity to prevent unexpected deadlocks - while (pollCount++ < Tx.Capacity) + var status = Binding.Baselib_RegisteredNetwork_Socket_UDP_ProcessSend(Baselib[0].m_Socket, &error); + + if (error.code != ErrorCode.Success) { - if (Binding.Baselib_RegisteredNetwork_Socket_UDP_ProcessSend(Baselib[0].m_Socket, &error) != Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending) - break; + UnityEngine.Debug.LogError(string.Format("Error on baselib processing send ({0})", error.code)); + MarkSocketAsNeedingRecreate(Baselib); + return; } - // At this point all the packets have been processed or the network can't send a packet right now - // so we yield execution to give the opportunity to other threads to process as the potential network - // pressure releases. - Binding.Baselib_Thread_YieldExecution(); - // Next step we wait until processing sends complete. - // The timeout was arbitrarily chosen as it seems to be enough for sending 5000 packets in our tests. - Binding.Baselib_RegisteredNetwork_Socket_UDP_WaitForCompletedSend(Baselib[0].m_Socket, 20, &error); - - var count = 0; - var resultBatchesCount = Tx.Capacity / k_ResultsBufferSize + 1; - while ((count = (int)Binding.Baselib_RegisteredNetwork_Socket_UDP_DequeueSend(Baselib[0].m_Socket, results, k_ResultsBufferSize, &error)) > 0) - { - if (error.code != ErrorCode.Success) - { - // copy recv flow? e.g. pass - return; - } - for (int i = 0; i < count; ++i) - { - // return results[i].status through userdata, mask? or allocate space at beginning? - // pass through a new NetworkPacketSender.? - Tx.ReleaseHandle((int)results[i].requestUserdata - 1); - } + if (status != Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending) + break; + } - if (resultBatchesCount-- < 0) // Deadlock guard - break; + var count = 0; + var resultBatchesCount = Tx.Capacity / k_RequestsBatchSize + 1; + while ((count = (int)Binding.Baselib_RegisteredNetwork_Socket_UDP_DequeueSend(Baselib[0].m_Socket, results, k_RequestsBatchSize, &error)) > 0) + { + if (error.code != ErrorCode.Success) + { + MarkSocketAsNeedingRecreate(Baselib); + return; + } + for (int i = 0; i < count; ++i) + { + // return results[i].status through userdata, mask? or allocate space at beginning? + // pass through a new NetworkPacketSender.? + Tx.ReleaseHandle((int)results[i].requestUserdata - 1); } - // We set the pollCount to zero that way we try and process any previous packets that may have failed because - // internal buffers were full via a EAGAIN error which we don't actually know happened but assume it may have happen - pollCount = 0; - - // InUse is not thread safe, needs to be called in a single threaded flush job - // We ensure we never process more than the actual capacity to prevent unexpected deadlocks - pendingSend = Tx.InUse > 0 && maxIterations-- > 0; + if (resultBatchesCount-- < 0) // Deadlock guard + break; } } } + [BurstCompile] struct ReceiveJob : IJob { @@ -418,100 +420,124 @@ struct ReceiveJob : IJob public unsafe void Execute() { - var count = 0; - var outstanding = Rx.InUse; var error = default(ErrorState); - var requests = stackalloc Binding.Baselib_RegisteredNetwork_Request[Rx.Capacity]; - if (outstanding > 0) - { - var pollCount = 0; - var status = default(Binding.Baselib_RegisteredNetwork_ProcessStatus); - while ((status = Binding.Baselib_RegisteredNetwork_Socket_UDP_ProcessRecv(Baselib[0].m_Socket, &error)) == Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending - && pollCount++ < Rx.Capacity) {} + // Update last update time in baselib data. + var baselib = Baselib[0]; + baselib.m_LastUpdateTime = Receiver.LastUpdateTime; + Baselib[0] = baselib; + + var pollCount = 0; + var status = default(Binding.Baselib_RegisteredNetwork_ProcessStatus); + while ((status = Binding.Baselib_RegisteredNetwork_Socket_UDP_ProcessRecv(Baselib[0].m_Socket, &error)) == Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending + && pollCount++ < Rx.Capacity) {} #if ENABLE_UNITY_COLLECTIONS_CHECKS - if (status == Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending) - { - UnityEngine.Debug.LogWarning("There are pending receive packets after the baselib process receive"); - } + if (status == Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending) + { + UnityEngine.Debug.LogWarning("There are pending receive packets after the baselib process receive"); + } #endif - var results = stackalloc Binding.Baselib_RegisteredNetwork_CompletionResult[outstanding]; + var results = stackalloc Binding.Baselib_RegisteredNetwork_CompletionResult[(int)k_RequestsBatchSize]; + var totalCount = 0; + var totalFailedCount = 0; + var count = 0; + do + { // Pop Completed Requests off the CompletionQ - count = (int)Binding.Baselib_RegisteredNetwork_Socket_UDP_DequeueRecv(Baselib[0].m_Socket, results, (uint)outstanding, &error); + count = (int)Binding.Baselib_RegisteredNetwork_Socket_UDP_DequeueRecv(Baselib[0].m_Socket, results, (uint)k_RequestsBatchSize, &error); if (error.code != ErrorCode.Success) { - Receiver.ReceiveErrorCode = (int)error.code; + Receiver.ReceiveErrorCode = (int)Error.StatusCode.NetworkSocketError; return; } - // Copy and run Append on each Packet. - - var address = default(NetworkInterfaceEndPoint); + totalCount += count; - var indices = stackalloc int[count]; + // Copy and run Append on each Packet. for (int i = 0; i < count; i++) { - var index = (int)results[i].requestUserdata - 1; - indices[i] = index; - if (results[i].status == Binding.Baselib_RegisteredNetwork_CompletionStatus.Failed) + { + totalFailedCount++; continue; + } var receivedBytes = (int)results[i].bytesTransferred; if (receivedBytes <= 0) continue; + var index = (int)results[i].requestUserdata - 1; + var packet = Rx.GetRequestFromHandle(index); var remote = packet.remoteEndpoint.slice; + var address = default(NetworkInterfaceEndPoint); address.dataLength = (int)remote.size; UnsafeUtility.MemCpy(address.data, (void*)remote.data, (int)remote.size); Receiver.AppendPacket(packet.payload.data, ref address, receivedBytes); - } - // Reuse the requests after they have been processed. - for (int i = 0; i < count; i++) - { - requests[i] = Rx.GetRequestFromHandle(indices[i]); - requests[i].requestUserdata = (IntPtr)indices[i] + 1; + Rx.ReleaseHandle(index); } } - - while (Rx.InUse < Rx.Capacity) + while (count == k_RequestsBatchSize); + + // All receive requests being marked as failed is as close as we're going to get to + // a signal that the socket has failed with the current baselib API (at least on + // platforms that use the basic POSIX sockets implementation under the hood). Note + // that we can't do the same check on send requests, since there might be legit + // scenarios where sends are failing temporarily without the socket being borked. + if (totalCount > 0 && totalFailedCount == totalCount) { - int handle = Rx.AcquireHandle(); - requests[count] = Rx.GetRequestFromHandle(handle); - requests[count].requestUserdata = (IntPtr)handle + 1; - ++count; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + UnityEngine.Debug.LogError("All socket receive requests were marked as failed, likely because socket itself has failed."); +#endif + MarkSocketAsNeedingRecreate(Baselib); } - if (count > 0) + + var result = ScheduleAllReceives(Baselib[0].m_Socket, ref Rx); + if (result < 0) { - Binding.Baselib_RegisteredNetwork_Socket_UDP_ScheduleRecv(Baselib[0].m_Socket, requests, (uint)count, &error); - if (error.code != ErrorCode.Success) - Receiver.ReceiveErrorCode = (int)error.code; + Receiver.ReceiveErrorCode = (int)result; + MarkSocketAsNeedingRecreate(Baselib); } } } - #endregion - [Conditional("UNITY_IOS")] - private void RecreateSocketIfAppWasSuspended(long currentUpdateTime) + private static void MarkSocketAsNeedingRecreate(NativeArray baselib) + { + var data = baselib[0]; + data.m_SocketStatus = SocketStatus.SocketNeedsRecreate; + baselib[0] = data; + } + + private void RecreateSocket(long updateTime) { var baselib = m_Baselib[0]; - var foregroundTime = AppForegroundTracker.LastForegroundTimestamp; - if (foregroundTime > 0 && baselib.m_LastUpdateTime > 0 && foregroundTime > baselib.m_LastUpdateTime) + // If we already recreated the socket in the last update or if we hit the limit of + // socket recreations, then something's wrong at the socket layer and recreating it + // likely won't solve the issue. Just fail the socket in that scenario. + if (baselib.m_LastSocketRecreateTime == baselib.m_LastUpdateTime || baselib.m_NumSocketRecreate >= k_MaxNumSocketRecreate) { - Bind(baselib.m_LocalEndpoint); + UnityEngine.Debug.LogError("Unrecoverable socket failure. An unknown condition is preventing the application from reliably creating sockets."); + baselib.m_SocketStatus = SocketStatus.SocketFailed; + m_Baselib[0] = baselib; } + else + { + UnityEngine.Debug.LogWarning("Socket error encountered; attempting recovery by creating a new one."); + Bind(baselib.m_LocalEndpoint); - baselib = m_Baselib[0]; - baselib.m_LastUpdateTime = currentUpdateTime; - m_Baselib[0] = baselib; + // Update last socket recreation time and number of socket recreations. + baselib = m_Baselib[0]; + baselib.m_LastSocketRecreateTime = updateTime; + baselib.m_NumSocketRecreate++; + m_Baselib[0] = baselib; + } } /// @@ -523,7 +549,14 @@ private void RecreateSocketIfAppWasSuspended(long currentUpdateTime) /// A to our newly created ScheduleReceive Job. public JobHandle ScheduleReceive(NetworkPacketReceiver receiver, JobHandle dep) { - RecreateSocketIfAppWasSuspended(receiver.LastUpdateTime); + if (m_Baselib[0].m_SocketStatus == SocketStatus.SocketNeedsRecreate) + RecreateSocket(receiver.LastUpdateTime); + + if (m_Baselib[0].m_SocketStatus == SocketStatus.SocketFailed) + { + receiver.ReceiveErrorCode = (int)Error.StatusCode.NetworkSocketError; + return dep; + } var job = new ReceiveJob { @@ -542,6 +575,9 @@ public JobHandle ScheduleReceive(NetworkPacketReceiver receiver, JobHandle dep) /// A to our newly created ScheduleSend Job. public JobHandle ScheduleSend(NativeQueue sendQueue, JobHandle dep) { + if (m_Baselib[0].m_SocketStatus != SocketStatus.SocketNormal) + return dep; + var job = new FlushSendJob { Baselib = m_Baselib, @@ -579,7 +615,7 @@ public unsafe int Bind(NetworkInterfaceEndPoint endpoint) checked((uint)configuration.receiveQueueCapacity), &error); if (error.code != ErrorCode.Success) - return (int)error.code == -1 ? -1 : -(int)error.code; + return (int)error.code == -1 ? (int)Error.StatusCode.NetworkSocketError : -(int)error.code; // Close old socket now that new one has been successfully created. if (m_Baselib[0].m_Socket.handle != IntPtr.Zero) @@ -596,31 +632,16 @@ public unsafe int Bind(NetworkInterfaceEndPoint endpoint) } // Schedule receive right away so we do not loose packets received before the first call to update - int count = 0; - var requests = stackalloc Binding.Baselib_RegisteredNetwork_Request[m_PayloadsRx.Capacity]; - while (m_PayloadsRx.InUse < m_PayloadsRx.Capacity) - { - int handle = m_PayloadsRx.AcquireHandle(); - requests[count] = m_PayloadsRx.GetRequestFromHandle(handle); - requests[count].requestUserdata = (IntPtr)handle + 1; - ++count; - } - if (count > 0) - { - Binding.Baselib_RegisteredNetwork_Socket_UDP_ScheduleRecv( - socket, - requests, - (uint)count, - &error); - // how should this be handled? what are the cases? - if (error.code != ErrorCode.Success) - return (int)error.code == -1 ? -1 : -(int)error.code; - } + var result = ScheduleAllReceives(socket, ref m_PayloadsRx); + if (result < 0) + return result; + #if ENABLE_UNITY_COLLECTIONS_CHECKS AllSockets.OpenSockets.Add(new SocketList.SocketId {socket = socket}); #endif baselib.m_Socket = socket; + baselib.m_SocketStatus = SocketStatus.SocketNormal; baselib.m_LocalEndpoint = GetLocalEndPoint(socket); m_Baselib[0] = baselib; @@ -717,6 +738,35 @@ private static unsafe void AbortSendMessage(ref NetworkInterfaceSendHandle handl baselib->m_PayloadsTx.ReleaseHandle(id); } + private static unsafe int ScheduleAllReceives(NetworkSocket socket, ref Payloads PayloadsRx) + { + var error = default(ErrorState); + + var requests = stackalloc Binding.Baselib_RegisteredNetwork_Request[(int)k_RequestsBatchSize]; + var count = 0; + do + { + count = 0; + while (count < k_RequestsBatchSize && PayloadsRx.InUse < PayloadsRx.Capacity) + { + int handle = PayloadsRx.AcquireHandle(); + requests[count] = PayloadsRx.GetRequestFromHandle(handle); + requests[count].requestUserdata = (IntPtr)handle + 1; + ++count; + } + if (count > 0) + { + Binding.Baselib_RegisteredNetwork_Socket_UDP_ScheduleRecv(socket, requests, (uint)count, &error); + // how should this be handled? what are the cases? + if (error.code != ErrorCode.Success) + return (int)error.code == -1 ? (int)Error.StatusCode.NetworkSocketError : -(int)error.code; + } + } + while (count == k_RequestsBatchSize); + + return 0; + } + bool ValidateParameters(BaselibNetworkParameter param) { if (param.receiveQueueCapacity <= 0) diff --git a/Runtime/DataStream.cs b/Runtime/DataStream.cs index 5aa35d5..408f42f 100644 --- a/Runtime/DataStream.cs +++ b/Runtime/DataStream.cs @@ -71,19 +71,13 @@ internal struct UIntFloat [StructLayout(LayoutKind.Sequential)] public unsafe struct DataStreamWriter { - struct IsLittleEndianStructKey {} - private static readonly SharedStatic m_IsLittleEndian = SharedStatic.GetOrCreate(); public static bool IsLittleEndian { get { - if (m_IsLittleEndian.Data == 0) - { - uint test = 1; - byte* testPtr = (byte*)&test; - m_IsLittleEndian.Data = testPtr[0] == 1 ? 1 : 2; - } - return m_IsLittleEndian.Data == 1; + uint test = 1; + byte* testPtr = (byte*)&test; + return testPtr[0] == 1; } } diff --git a/Runtime/NetworkConnection.cs b/Runtime/NetworkConnection.cs index 51027af..331f882 100644 --- a/Runtime/NetworkConnection.cs +++ b/Runtime/NetworkConnection.cs @@ -74,8 +74,11 @@ public enum StatusCode /// Internal send handle is invalid. NetworkSendHandleInvalid = -8, - /// Tried to create an on a non-loopback address. + /// Tried to create an on a non-loopback address. NetworkArgumentMismatch = -9, + + /// The underlying network socket has failed. + NetworkSocketError = -10, } } diff --git a/Runtime/NetworkDriver.cs b/Runtime/NetworkDriver.cs index 99cc569..29ccb8a 100644 --- a/Runtime/NetworkDriver.cs +++ b/Runtime/NetworkDriver.cs @@ -462,6 +462,8 @@ public bool Equals(Connection connection) int m_NetworkInterfaceIndex; NetworkSendInterface m_NetworkSendInterface; + internal INetworkInterface NetworkInterface => s_NetworkInterfaces[m_NetworkInterfaceIndex]; + int m_NetworkProtocolIndex; NetworkProtocol m_NetworkProtocolInterface; diff --git a/Tests/Editor/RelayNetworkDriverTests.cs b/Tests/Editor/RelayNetworkDriverTests.cs index 5ef44cd..e98ff15 100644 --- a/Tests/Editor/RelayNetworkDriverTests.cs +++ b/Tests/Editor/RelayNetworkDriverTests.cs @@ -29,6 +29,8 @@ public void RelayCheckStructSizes() } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 + [Ignore("Unstable in APVs. See MTT-4345.")] public void RelayNetworkDriver_Bind_Succeed() { using var server = new RelayServerMock("127.0.0.1", m_port++); @@ -46,6 +48,8 @@ public void RelayNetworkDriver_Bind_Succeed() } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 + [Ignore("Unstable in APVs. See MTT-4345.")] public void RelayNetworkDriver_Bind_Retry() { const int k_RetryCount = 10; @@ -80,6 +84,7 @@ public void RelayNetworkDriver_Bind_Retry() } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 public void RelayNetworkDriver_Bind_Fail() { using var server = new RelayServerMock("127.0.0.1", m_port++); @@ -113,6 +118,7 @@ public void RelayNetworkDriver_Bind_Fail() } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 public void RelayNetworkDriver_Listen_Succeed() { using var server = new RelayServerMock("127.0.0.1", m_port++); @@ -129,6 +135,7 @@ public void RelayNetworkDriver_Listen_Succeed() } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 public void RelayNetworkDriver_Connect_Succeed() { using var server = new RelayServerMock("127.0.0.1", m_port++); @@ -177,6 +184,7 @@ public void RelayNetworkDriver_Connect_Succeed() } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 public void RelayNetworkDriver_Connect_Retry() { const int k_RetryCount = 10; @@ -231,6 +239,7 @@ public void RelayNetworkDriver_Connect_Retry() } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 public void RelayNetworkDriver_Disconnect_Succeed() { using var server = new RelayServerMock("127.0.0.1", m_port++); @@ -278,6 +287,7 @@ public void RelayNetworkDriver_Disconnect_Succeed() } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 public void RelayNetworkDriver_Send_Succeed() { const int k_PayloadSize = 100; @@ -323,6 +333,7 @@ public void RelayNetworkDriver_Send_Succeed() } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 public void RelayNetworkDriver_AllocationTimeOut() { using var server = new RelayServerMock("127.0.0.1", m_port++); diff --git a/Tests/Runtime/BaselibNetworkInterfaceTests.cs b/Tests/Runtime/BaselibNetworkInterfaceTests.cs index 6bfa6e0..31dcd42 100644 --- a/Tests/Runtime/BaselibNetworkInterfaceTests.cs +++ b/Tests/Runtime/BaselibNetworkInterfaceTests.cs @@ -1,7 +1,6 @@ using System.Threading; using NUnit.Framework; using Unity.Networking.Transport; -using Unity.Networking.Transport.Utilities; using UnityEngine; using UnityEngine.TestTools; using System.Linq; @@ -49,9 +48,15 @@ public unsafe void Baselib_Send_WaitForCompletion() } } + private void FakeSocketFailure(BaselibNetworkInterface baselibInterface) + { + var baselib = baselibInterface.m_Baselib[0]; + baselib.m_SocketStatus = BaselibNetworkInterface.SocketStatus.SocketNeedsRecreate; + baselibInterface.m_Baselib[0] = baselib; + } + [Test] - [UnityPlatform(include = new[] { RuntimePlatform.IPhonePlayer })] - public void Baselib_AfterAppSuspension_SocketIsRecreated() + public void Baselib_AfterSocketFailure_SocketIsRecreated() { using (var baselibInterface = new BaselibNetworkInterface()) using (var dummyDriver = NetworkDriver.Create()) @@ -69,34 +74,75 @@ public void Baselib_AfterAppSuspension_SocketIsRecreated() packetReceiver.m_Driver = dummyDriver; baselibInterface.ScheduleReceive(packetReceiver, default).Complete(); - // Fake an app suspension by manually calling the focus callback. We add sleeps - // around the call to ensure the timestamp is different from the receice jobs. - Thread.Sleep(5); - AppForegroundTracker.OnFocusChanged(true); - Thread.Sleep(5); + // Sleep to ensure different update times. + Thread.Sleep(2); + + FakeSocketFailure(baselibInterface); dummyDriver.ScheduleUpdate().Complete(); packetReceiver.m_Driver = dummyDriver; baselibInterface.ScheduleReceive(packetReceiver, default).Complete(); Assert.AreNotEqual(socket, baselibInterface.m_Baselib[0].m_Socket); + + LogAssert.Expect(LogType.Warning, "Socket error encountered; attempting recovery by creating a new one."); + } + } + + [Test] + public void Baselib_AfterBackToBackSocketFailures_SocketIsFailed() + { + using (var baselibInterface = new BaselibNetworkInterface()) + using (var dummyDriver = NetworkDriver.Create()) + { + var settings = new NetworkSettings(); + baselibInterface.Initialize(settings); + baselibInterface.CreateInterfaceEndPoint(NetworkEndPoint.AnyIpv4, out var endpoint); + Assert.Zero(baselibInterface.Bind(endpoint)); + + var packetReceiver = new NetworkPacketReceiver(); + + dummyDriver.ScheduleUpdate().Complete(); + packetReceiver.m_Driver = dummyDriver; + baselibInterface.ScheduleReceive(packetReceiver, default).Complete(); + + // Sleep to ensure different update times. + Thread.Sleep(2); + + FakeSocketFailure(baselibInterface); + + dummyDriver.ScheduleUpdate().Complete(); + packetReceiver.m_Driver = dummyDriver; + baselibInterface.ScheduleReceive(packetReceiver, default).Complete(); + + LogAssert.Expect(LogType.Warning, "Socket error encountered; attempting recovery by creating a new one."); + + // Sleep to ensure different update times. + Thread.Sleep(2); + + FakeSocketFailure(baselibInterface); + + dummyDriver.ScheduleUpdate().Complete(); + packetReceiver.m_Driver = dummyDriver; + baselibInterface.ScheduleReceive(packetReceiver, default).Complete(); + + Assert.AreEqual((int)Error.StatusCode.NetworkSocketError, dummyDriver.ReceiveErrorCode); + + LogAssert.Expect(LogType.Error, "Unrecoverable socket failure. An unknown condition is preventing the application from reliably creating sockets."); + LogAssert.Expect(LogType.Error, "Error on receive, errorCode = -10"); } } [Test] - [UnityPlatform(include = new[] { RuntimePlatform.IPhonePlayer })] - public void Baselib_AfterAppSuspension_CanSendReceive() + public void Baselib_AfterSocketRecreation_CanSendReceive() { using (var server = NetworkDriver.Create()) using (var client = NetworkDriver.Create()) { ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out var connection); - // Fake an app suspension by manually calling the focus callback. We add sleeps - // around the call to ensure the timestamp is different from the driver updates. - Thread.Sleep(5); - AppForegroundTracker.OnFocusChanged(true); - Thread.Sleep(5); + var clientBaselibInterface = (BaselibNetworkInterface)client.NetworkInterface; + FakeSocketFailure(clientBaselibInterface); // Let the server and client recreate their sockets. client.ScheduleUpdate().Complete(); diff --git a/Tests/Runtime/ConnectDisconnectTests.cs b/Tests/Runtime/ConnectDisconnectTests.cs index e952b50..94304dd 100644 --- a/Tests/Runtime/ConnectDisconnectTests.cs +++ b/Tests/Runtime/ConnectDisconnectTests.cs @@ -98,6 +98,7 @@ public void ConnectDisconnect_MultipleClients( } [Test] + [Ignore("Unstable in APVs. See MTT-4345.")] public void ConnectDisconnect_ConnectSucceedsAfterRetrying( [ValueSource("s_SecureModeParameters")] SecureProtocolMode secureMode) { diff --git a/Tests/Runtime/DisconnectTimeoutTests.cs b/Tests/Runtime/DisconnectTimeoutTests.cs index c414760..98b2b42 100644 --- a/Tests/Runtime/DisconnectTimeoutTests.cs +++ b/Tests/Runtime/DisconnectTimeoutTests.cs @@ -85,6 +85,7 @@ public void DisconnectTimeout_ReachedWithInfrequentHeartbeats() } [Test] + [Ignore("Unstable in APVs. See MTT-4345.")] public void DisconnectTimeout_NotReachedWithFrequentHeartbeats() { var settings = new NetworkSettings(); diff --git a/Tests/Runtime/SendMessageTests.cs b/Tests/Runtime/SendMessageTests.cs index d8a3d54..54c7ae9 100644 --- a/Tests/Runtime/SendMessageTests.cs +++ b/Tests/Runtime/SendMessageTests.cs @@ -119,6 +119,7 @@ public void SendMessage_PingPong_MaxLength( } [UnityTest, UnityPlatform(RuntimePlatform.LinuxEditor, RuntimePlatform.WindowsEditor, RuntimePlatform.OSXEditor)] + [Ignore("Unstable in APVs. See MTT-4345.")] public IEnumerator SendMessage_OverflowedReceiveBuffer() { var settings = new NetworkSettings(); diff --git a/ValidationExceptions.json b/ValidationExceptions.json index f22cd94..59678d2 100644 --- a/ValidationExceptions.json +++ b/ValidationExceptions.json @@ -3,12 +3,12 @@ { "ValidationTest": "Restricted File Type Validation", "ExceptionMessage": "/Samples~/CustomNetworkInterface/Scripts/network.bindings~/build.bat cannot be included in a package.", - "PackageVersion": "1.1.0" + "PackageVersion": "1.2.0" }, { "ValidationTest": "Restricted File Type Validation", "ExceptionMessage": "/Samples~/CustomNetworkInterface/Scripts/network.bindings~/shell.bat cannot be included in a package.", - "PackageVersion": "1.1.0" + "PackageVersion": "1.2.0" } ], "WarningExceptions": [] diff --git a/package.json b/package.json index dc074f5..0752dc0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.transport", "displayName": "Unity Transport", - "version": "1.1.0", + "version": "1.2.0", "unity": "2020.3", "unityRelease": "0f1", "description": "Unity network transport layer - the low-level interface for sending UDP data", @@ -10,16 +10,13 @@ "com.unity.burst": "1.6.6", "com.unity.mathematics": "1.2.6" }, - "_upm": { - "changelog": "### New features\n* A `DataStreamReader` can now be passed to another job without triggering the job safety system.\n* A `GetRelayConnectionStatus` method has been added to `NetworkDriver` to query the status of the connection to the Relay server.\n\n### Changes\n* `NetworkSettings.WithDataStreamParameters` is now obsolete. The functionality still works and will remain supported for version 1.X of the package, but will be removed in version 2.0. The reason for the removal is that in 2.0 the data stream size is always dynamically-sized to avoid out-of-memory errors.\n* `NetworkSettings.WithPipelineParameters` is now obsolete. The functionality still works and will remain supported for version 1.X of the package, but will be removed in version 2.0, where pipeline buffer sizing is handled internally.\n* Updated Burst dependency to 1.6.6.\n* Updated Collections dependency to 1.2.4.\n* Updated Mathematics dependency to 1.2.6.\n\n### Fixes\n* `BeginSend` would not return an error if called on a closed connection before the next `ScheduleUpdate` call.\n* Fixed a warning if using the default maximum payload size with DTLS.\n* Removed an error log when receiving messages on a closed DTLS connection (this scenario is common if there were in-flight messages at the moment of disconnection).\n* Fix broken link in package documentation." - }, "upmCi": { - "footprint": "cd3960b74c34568953deb592a44e23402c3a914c" + "footprint": "b86aef366145fa59d88b43f35ef32b38cf576ef6" }, "repository": { "url": "https://github.cds.internal.unity3d.com/unity/com.unity.transport.git", "type": "git", - "revision": "ce258b46039e4d4937778aa8d02c6532b9ea0888" + "revision": "b4fadab8f255b0b13f371bea8d2819c2ca472648" }, "samples": [ {