fix: Fixes stuck connections (#1708)

This commit is contained in:
Kamron Batman 2024-04-03 08:14:41 -07:00 committed by GitHub
parent 39215935cf
commit 183f6fa4ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 136 additions and 120 deletions

View file

@ -134,13 +134,16 @@ public static class Firewall
_isBlockedCache.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void RemoveEntry(IFirewallEntry entry)
{
if (entry != null)
if (entry == null)
{
_firewallSet.Remove(entry);
_isBlockedCache.Clear();
return;
}
_firewallSet.Remove(entry);
_isBlockedCache.Clear();
}
private class InternalValidationEntry : BaseFirewallEntry

View file

@ -43,6 +43,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(NetState));
private static readonly TimeSpan ConnectingSocketIdleLimit = TimeSpan.FromMilliseconds(2000); // 2 seconds
private const int RecvPipeSize = 1024 * 64;
private const int SendPipeSize = 1024 * 256;
private const int GumpCap = 512;
@ -60,6 +61,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
public static NetStateCreatedCallback CreatedCallback { get; set; }
private static readonly SortedSet<NetState> _connecting = new(NetStateConnectingComparer.Instance);
private static readonly HashSet<NetState> _instances = new(2048);
public static IReadOnlySet<NetState> Instances => _instances;
@ -84,6 +86,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
// Speed Hack Prevention
internal long _movementCredit;
internal long _nextMovementTime;
private IAccount _account;
internal enum ParserState
{
@ -234,7 +237,19 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
public ServerInfo[] ServerInfo { get; set; }
public IAccount Account { get; set; }
public IAccount Account
{
get => _account;
set
{
if (_account != null)
{
_connecting.Remove(this);
}
_account = value;
}
}
public string Assistant { get; set; }
@ -690,7 +705,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
case ProtocolState.LoginServer_ServerSelectAck:
{
#if STRICT_UO_PROTOCOL
HandleError(packetId, packetLength);
HandleError(packetId, packetLength);
#else
// Reset the state because CUO/Orion do not reconnect
_parserState = ParserState.AwaitingNextPacket;
@ -867,6 +882,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
{
_flushQueued = false;
// We don't have a running check since we need to send the last bits of data even after a disconnect, but before a dispose.
if (Connection == null)
{
return true;
@ -892,12 +908,14 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
{
logger.Debug(ex, "Disconnected due to a socket exception");
Disconnect(string.Empty);
return true;
}
}
catch (Exception ex)
{
Disconnect($"Disconnected with error: {ex}");
TraceException(ex);
return true;
}
if (bytesWritten > 0)
@ -957,6 +975,33 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
_nextActivityCheck = Core.TickCount + 90000;
}
private static void DisconnectUnattachedSockets()
{
var now = Core.Now;
// Clear out any sockets that have been connecting for too long
while (_connecting.Count > 0)
{
var ns = _connecting.Min;
var socketTime = ns.ConnectedOn;
// If the socket has been connected for less than 2 seconds, we can stop checking
if (now - socketTime < ConnectingSocketIdleLimit)
{
break;
}
// Socket must have finished the entire authentication process or be forcibly disconnected.
if (!ns.Running || !ns.SentFirstPacket || !ns.Seeded || ns.Account == null)
{
// Not sending a message because it will fill up the logs.
ns.Disconnect(null);
}
_connecting.Remove(ns);
}
}
public static void FlushAll()
{
while (_flushPending.Count != 0)
@ -967,6 +1012,8 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
public static void Slice()
{
DisconnectUnattachedSockets();
const int maxEntriesPerLoop = 32;
var count = 0;
while (++count <= maxEntriesPerLoop && TcpServer.ConnectedQueue.TryDequeue(out var ns))
@ -974,6 +1021,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
CreatedCallback?.Invoke(ns);
_instances.Add(ns);
_connecting.Add(ns); // Add to the connecting set, and remove them when they authenticated.
ns.LogInfo($"Connected. [{Instances.Count} Online]");
}
@ -1111,17 +1159,6 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
_running = false;
#if THREADGUARD
if (Thread.CurrentThread != Core.Thread)
{
Utility.PushColor(ConsoleColor.Red);
Console.WriteLine("Attempting to disconnect a netstate from an invalid thread!");
Console.WriteLine(new StackTrace());
Utility.PopColor();
return;
}
#endif
_disconnectReason = reason;
_disposed.Enqueue(this);
}
@ -1165,17 +1202,6 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
throw new Exception("Disconnected a NetState that is still running.");
}
#if THREADGUARD
if (Thread.CurrentThread != Core.Thread)
{
Utility.PushColor(ConsoleColor.Red);
Console.WriteLine("Attempting to dispose a netstate from an invalid thread!");
Console.WriteLine(new StackTrace());
Utility.PopColor();
return;
}
#endif
var m = Mobile;
if (m?.NetState == this)
{
@ -1183,6 +1209,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
}
_instances.Remove(this);
_connecting.Remove(this);
try
{
@ -1210,8 +1237,44 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
CityInfo = null;
Connection = null;
var count = Instances.Count;
var count = _instances.Count;
LogInfo(a != null ? $"Disconnected. [{count} Online] [{a}]" : $"Disconnected. [{count} Online]");
}
private class NetStateConnectingComparer : IComparer<NetState>
{
public static readonly IComparer<NetState> Instance = new NetStateConnectingComparer();
public int Compare(NetState x, NetState y)
{
if (x == null && y == null)
{
return 0;
}
if (x == null)
{
return -1;
}
if (y == null)
{
return 1;
}
if (ReferenceEquals(x, y))
{
return 0;
}
var connectedOn = x.ConnectedOn.CompareTo(y.ConnectedOn);
if (connectedOn != 0)
{
return connectedOn;
}
return x.CompareTo(y);
}
}
}

View file

@ -13,11 +13,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Threading;
using Server.Logging;
namespace Server.Network;
@ -26,12 +25,8 @@ public static class PingServer
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(PingServer));
private const int MaxConnectionsPerLoop = 128;
public static int MaxQueued { get; set; }
private static readonly ConcurrentQueue<(UdpClient, UdpReceiveResult)> _udpResponseQueue = new();
public static UdpClient[] Listeners { get; private set; }
public static bool Enabled { get; private set; }
@ -57,7 +52,6 @@ public static class PingServer
foreach (var serverIpep in ServerConfiguration.Listeners)
{
var cancellationToken = Core.ClosingTokenSource.Token;
var ipep = new IPEndPoint(serverIpep.Address, Port);
var listener = CreateListener(ipep);
@ -76,7 +70,7 @@ public static class PingServer
}
listeners.Add(listener);
Task.Run(() => BeginAcceptingUdpRequest(listener), cancellationToken).ConfigureAwait(false);
new Thread(BeginAcceptingUdpRequest).Start(listener);
}
foreach (var ipep in listeningAddresses)
@ -87,22 +81,6 @@ public static class PingServer
Listeners = listeners.ToArray();
}
public static void Slice()
{
if (!Enabled)
{
return;
}
int count = 0;
while (++count <= MaxConnectionsPerLoop && _udpResponseQueue.TryDequeue(out var udpTuple))
{
var (listener, result) = udpTuple;
SendResponse(listener, result.Buffer, result.RemoteEndPoint);
}
}
public static UdpClient CreateListener(IPEndPoint ipep)
{
var listener = new Socket(ipep.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
@ -140,8 +118,13 @@ public static class PingServer
return null;
}
private static async void BeginAcceptingUdpRequest(UdpClient listener)
private static async void BeginAcceptingUdpRequest(object state)
{
if (state is not UdpClient listener)
{
return;
}
var cancellationToken = Core.ClosingTokenSource.Token;
while (!cancellationToken.IsCancellationRequested)
@ -149,12 +132,7 @@ public static class PingServer
try
{
var result = await listener.ReceiveAsync(cancellationToken);
if (_udpResponseQueue.Count < MaxQueued)
{
_udpResponseQueue.Enqueue((listener, result));
}
await listener.SendAsync(result.Buffer, result.RemoteEndPoint, cancellationToken);
}
catch
{
@ -162,16 +140,4 @@ public static class PingServer
}
}
}
private static async Task SendResponse(UdpClient listener, byte[] data, IPEndPoint ipep)
{
try
{
await listener.SendAsync(data, ipep, Core.ClosingTokenSource.Token);
}
catch
{
// ignored
}
}
}

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: TcpServer.cs *
* *
@ -23,7 +23,6 @@ using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Server.Logging;
using Server.Misc;
@ -33,7 +32,6 @@ public static class TcpServer
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(TcpServer));
private const long MaximumSocketIdleDelay = 2000; // 2 seconds
private const long ListenerErrorMessageDelay = 10000; // 10 seconds
private static long _nextMaximumSocketsReachedMessage;
@ -47,9 +45,6 @@ public static class TcpServer
public static IPEndPoint[] ListeningAddresses { get; private set; }
public static Socket[] Listeners { get; private set; }
// By default should sort T1 then T2
public static readonly SortedSet<(long ConnectedAt, NetState NetState)> _socketsConnecting = [];
public static ConcurrentQueue<NetState> ConnectedQueue { get; } = [];
public static void Configure()
@ -109,28 +104,34 @@ public static class TcpServer
return null;
}
private static async Task BeginAcceptingSockets(Socket listener)
private static async void BeginAcceptingSockets(object state)
{
if (state is not Socket listener)
{
return;
}
var cancellationToken = Core.ClosingTokenSource.Token;
try
while (!cancellationToken.IsCancellationRequested)
{
var socket = await listener.AcceptAsync(cancellationToken);
_connectingQueue.Enqueue(socket);
_queueSemaphore.Release();
}
catch(OperationCanceledException)
{
return;
try
{
var socket = await listener.AcceptAsync(cancellationToken);
_connectingQueue.Enqueue(socket);
_queueSemaphore.Release();
}
catch (OperationCanceledException)
{
return;
}
catch
{
// ignored
}
}
if (cancellationToken.IsCancellationRequested)
{
listener.Close();
return;
}
Task.Run(() => BeginAcceptingSockets(listener), cancellationToken).ConfigureAwait(false);
listener.Close();
}
private static void ProcessConnections()
@ -164,7 +165,7 @@ public static class TcpServer
{
listeners.Add(listener);
Task.Run(() => BeginAcceptingSockets(listener), cancellationToken).ConfigureAwait(false);
new Thread(BeginAcceptingSockets).Start(listener);
}
}
@ -198,6 +199,10 @@ public static class TcpServer
{
return;
}
catch (Exception e)
{
logger.Error(e, "Error occurred in ProcessConnections");
}
}
}
@ -221,24 +226,6 @@ public static class TcpServer
var ipLimiter = IPLimiter.Enabled;
try
{
// Clear out any sockets that have been connecting for too long
while (_socketsConnecting.Count > 0)
{
var socketTime = _socketsConnecting.Min; // Earliest connected socket
if (Core.TickCount - socketTime.ConnectedAt <= MaximumSocketIdleDelay)
{
break;
}
var socketToCheck = socketTime.NetState;
if (socketToCheck.Running && !socketToCheck.Seeded)
{
socketToCheck.Disconnect(null);
}
_socketsConnecting.Remove(socketTime);
}
var remoteIP = ((IPEndPoint)socket.RemoteEndPoint)!.Address;
if (NetState.Instances.Count >= MaxConnections)
@ -279,7 +266,6 @@ public static class TcpServer
}
var ns = new NetState(socket);
_socketsConnecting.Add((Core.TickCount, ns));
ConnectedQueue.Enqueue(ns);
}
catch