diff --git a/Projects/Server/Network/Encryption/GameEncryption.cs b/Projects/Server/Network/Encryption/GameEncryption.cs index 78c219b63..fde81188c 100644 --- a/Projects/Server/Network/Encryption/GameEncryption.cs +++ b/Projects/Server/Network/Encryption/GameEncryption.cs @@ -14,8 +14,6 @@ *************************************************************************/ using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Security.Cryptography; using Server.Logging; diff --git a/Projects/Server/Network/NetState/NetState.Network.cs b/Projects/Server/Network/NetState/NetState.Network.cs index 494492cc1..3aad63146 100644 --- a/Projects/Server/Network/NetState/NetState.Network.cs +++ b/Projects/Server/Network/NetState/NetState.Network.cs @@ -32,6 +32,9 @@ public partial class NetState private const int SendBufferSize = 1024 * 256; // 256KB send buffers private const int MaxConnections = 4096; // Max concurrent connections + private static readonly Queue _disposed = []; + private static readonly TimeSpan ConnectingSocketIdleLimit = TimeSpan.FromMilliseconds(5000); // 5 seconds + // Socket manager handles buffer pools, socket lifecycle, and I/O operations private static RingSocketManager _socketManager; @@ -46,6 +49,9 @@ public partial class NetState private static int _pendingAcceptCount; private const int PendingAcceptsPerListener = 32; + private const long AliveCheckIntervalMs = 5000; + private static long _nextAliveCheck; + /// /// Gets the IORingGroup instance for socket operations. /// @@ -293,6 +299,13 @@ public partial class NetState if (!ns.SentFirstPacket || !ns.Seeded) { ns.Disconnect(null); + + // Force immediate cleanup - these are unauthenticated connections + // where graceful disconnect can get stuck with pending sends. + if (ns._socket is { DisconnectPending: true }) + { + _socketManager.DisconnectImmediate(ns._socket); + } } } } @@ -321,6 +334,7 @@ public partial class NetState public static void Slice() { + var curTicks = Core.TickCount; DisconnectUnattachedSockets(); // Process throttled states @@ -363,6 +377,7 @@ public partial class NetState // Verify generation via object identity to avoid stale completion issues if (nsRecv != null && nsRecv._socket == evt.Socket) { + nsRecv.NextActivityCheck = curTicks + 30000; HandleDataReceived(nsRecv, evt.BytesTransferred); } break; @@ -375,7 +390,7 @@ public partial class NetState if (nsSend != null && nsSend._socket == evt.Socket) { // Update activity check on successful send - nsSend.NextActivityCheck = Core.TickCount + 90000; + nsSend.NextActivityCheck = curTicks + 30000; } break; } @@ -436,7 +451,16 @@ public partial class NetState // Process disposes while (_disposed.TryDequeue(out var ns)) { - ns.Dispose(); + ns.DisposeInternal(); + } + + // Check for dead connections AFTER processing all completions. + // Recv completions reset NextActivityCheck, so after a server stall, + // buffered client pings update timestamps before this check fires. + if (curTicks - _nextAliveCheck >= 0) + { + _nextAliveCheck = curTicks + AliveCheckIntervalMs; + CheckAllAlive(); } } diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs index 36a116142..21be39811 100755 --- a/Projects/Server/Network/NetState/NetState.cs +++ b/Projects/Server/Network/NetState/NetState.cs @@ -21,7 +21,6 @@ using Server.Logging; using Server.Menus; using System; using System.Buffers; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Net; @@ -34,14 +33,12 @@ public partial class NetState : IComparable, IValueLinkListNode _flushPending = new(2048); private static readonly Queue _pendingDisconnects = new(256); // Processed AFTER flush - private static readonly ConcurrentQueue _disposed = new(); private static readonly Queue _throttled = new(256); private static readonly Queue _throttledPending = new(256); @@ -104,11 +101,6 @@ public partial class NetState : IComparable, IValueLinkListNode, IValueLinkListNode= 0) + { + return; + } + + if (_socket.DisconnectPending) + { + LogInfo("Force disconnecting stuck socket..."); + _socketManager.DisconnectImmediate(_socket); + } + else { LogInfo("Disconnecting due to inactivity..."); Disconnect("Disconnecting due to inactivity."); @@ -1031,10 +1033,14 @@ public partial class NetState : IComparable, IValueLinkListNode Dispose(); + // Do not run this directly. Use Disconnect instead. // This is available for testing cleanup only. + [Obsolete("Use Disconnect instead")] public void Dispose() { + var wasRunning = _running; _running = false; // It's possible we could queue for dispose multiple times if (_socket == null) @@ -1045,9 +1051,8 @@ public partial class NetState : IComparable, IValueLinkListNode - +