fix: Fix IORing disconnect issues. (#2335)
### Summary - Bump IORingGroup 1.0.0 → 1.0.1 — fixes a disconnect handling bug in the native ring layer - Fix ghost NetStates — Dispose() set _running = false before checking it, so the "force immediate disconnect" path was dead code. Capture wasRunning before clearing it, add [Obsolete] guard, and route internal callers through DisposeInternal() - Fix unauthenticated socket cleanup — graceful disconnect on unauthed connections could get stuck with pending sends; now force-immediate after Disconnect() if DisconnectPending is already set - Replace ConcurrentQueue<NetState> _disposed with Queue<NetState> — server is single-threaded; moved the field into the Network partial class where it's consumed - Move ConnectingSocketIdleLimit into the Network partial class alongside DisconnectUnattachedSockets - Reset activity timer on receive, not just send — receiving data directly proves liveness instead of relying on the ping→pong→send round-trip to reset the timer - Move CheckAllAlive from Timer into Slice — the timer fired before I/O completions were processed, so after server stalls (world saves), buffered client pings hadn't reset timestamps yet, causing false disconnects. Now runs at the end of Slice() after all recv completions are handled - Lower inactivity timeout 90s → 30s, check interval 90s → 5s — clients ping every ~1s, so 30s of silence is ~30 missed pings; worst-case detection drops from ~180s to ~35s - Simplify CheckAlive — early-return when socket is null or alive; force-kill stuck DisconnectPending sockets immediately instead of calling Disconnect() again - Remove unused imports from GameEncryption.cs
This commit is contained in:
parent
4b392079e9
commit
6ffb63ec82
4 changed files with 43 additions and 16 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<NetState> _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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IORingGroup instance for socket operations.
|
||||
/// </summary>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<NetState>, IValueLinkListNode<NetSta
|
|||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(NetState));
|
||||
|
||||
private static readonly TimeSpan ConnectingSocketIdleLimit = TimeSpan.FromMilliseconds(5000); // 5 seconds
|
||||
private const int HuePickerCap = 512;
|
||||
private const int MenuCap = 512;
|
||||
private const int PacketPerSecondThreshold = 3000;
|
||||
|
||||
private static readonly Queue<NetState> _flushPending = new(2048);
|
||||
private static readonly Queue<NetState> _pendingDisconnects = new(256); // Processed AFTER flush
|
||||
private static readonly ConcurrentQueue<NetState> _disposed = new();
|
||||
private static readonly Queue<NetState> _throttled = new(256);
|
||||
private static readonly Queue<NetState> _throttledPending = new(256);
|
||||
|
||||
|
|
@ -104,11 +101,6 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
|||
ConfigureNetwork();
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
Timer.DelayCall(TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1.5), CheckAllAlive);
|
||||
}
|
||||
|
||||
// Internal constructor for accepted sockets
|
||||
private NetState(RingSocket socket, IPAddress address)
|
||||
{
|
||||
|
|
@ -938,7 +930,17 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
|||
|
||||
public void CheckAlive(long curTicks)
|
||||
{
|
||||
if (_socket != null && NextActivityCheck - curTicks < 0)
|
||||
if (_socket == null || NextActivityCheck - curTicks >= 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<NetState>, IValueLinkListNode<NetSta
|
|||
}
|
||||
}
|
||||
|
||||
private void DisposeInternal() => 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<NetState>, IValueLinkListNode<NetSta
|
|||
TraceDisconnect(_disconnectReason, _toString);
|
||||
|
||||
// If still running, force immediate disconnect
|
||||
if (_running)
|
||||
if (wasRunning)
|
||||
{
|
||||
_running = false;
|
||||
_socketManager?.DisconnectImmediate(_socket);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue