From 0e5d53d36534afd6cbce1125f7c8c8f47be01760 Mon Sep 17 00:00:00 2001 From: Gabriel Scatolin <17441745+CypherPotato@users.noreply.github.com> Date: Tue, 26 May 2026 15:01:12 -0300 Subject: [PATCH] Harden listener restart lifecycle after rebuild changes --- cadente/Sisk.Cadente/HttpHost.cs | 97 +++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 15 deletions(-) diff --git a/cadente/Sisk.Cadente/HttpHost.cs b/cadente/Sisk.Cadente/HttpHost.cs index cef9ee6..214072b 100644 --- a/cadente/Sisk.Cadente/HttpHost.cs +++ b/cadente/Sisk.Cadente/HttpHost.cs @@ -21,7 +21,7 @@ namespace Sisk.Cadente; public sealed class HttpHost : IDisposable { private readonly IPEndPoint _endpoint; - private readonly Socket _listener; + private Socket _listener; // cache line padding to reduce false sharing private volatile bool _disposedValue; @@ -29,6 +29,7 @@ public sealed class HttpHost : IDisposable { private readonly SocketAsyncEventArgs [] _acceptArgsPool; private readonly int [] _acceptArgsAvailable; + private int _listenerRestarting = 0; private const int AcceptPoolSize = 8; private const int ListenerAcceptRetryDelayMilliseconds = 250; @@ -69,7 +70,7 @@ public sealed class HttpHost : IDisposable { /// The to listen on. public HttpHost ( IPEndPoint endpoint ) { _endpoint = endpoint; - _listener = new Socket ( endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp ); + _listener = CreateListenerSocket (); _acceptArgsPool = new SocketAsyncEventArgs [ AcceptPoolSize ]; _acceptArgsAvailable = new int [ AcceptPoolSize ]; @@ -97,7 +98,12 @@ public void Start () { return; ObjectDisposedException.ThrowIf ( _disposedValue, this ); - ConfigureListenerSocket (); + try { + _listener.Dispose (); + } + catch { } + + _listener = CreateListenerSocket (); _listener.Bind ( _endpoint ); _listener.Listen ( backlog: 4096 ); // Alto para burst de conexões _isListening = true; @@ -109,23 +115,27 @@ public void Start () { } [MethodImpl ( MethodImplOptions.AggressiveInlining )] - private void ConfigureListenerSocket () { - _listener.NoDelay = true; - _listener.LingerState = new LingerOption ( false, 0 ); + private Socket CreateListenerSocket () { + var listener = new Socket ( _endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp ); + + listener.NoDelay = true; + listener.LingerState = new LingerOption ( false, 0 ); // Buffers grandes para o listener reduzem syscalls - _listener.ReceiveBufferSize = 128 * 1024; - _listener.SendBufferSize = 128 * 1024; + listener.ReceiveBufferSize = 128 * 1024; + listener.SendBufferSize = 128 * 1024; - if (_listener.AddressFamily == AddressFamily.InterNetworkV6 && _endpoint.Address.Equals ( IPAddress.IPv6Any )) { - _listener.DualMode = true; + if (listener.AddressFamily == AddressFamily.InterNetworkV6 && _endpoint.Address.Equals ( IPAddress.IPv6Any )) { + listener.DualMode = true; } - _listener.SetSocketOption ( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true ); - _listener.SetSocketOption ( SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true ); - _listener.SetSocketOption ( SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 3 ); - _listener.SetSocketOption ( SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 300 ); - _listener.SetSocketOption ( SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 3 ); + listener.SetSocketOption ( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true ); + listener.SetSocketOption ( SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true ); + listener.SetSocketOption ( SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 3 ); + listener.SetSocketOption ( SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 300 ); + listener.SetSocketOption ( SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 3 ); + + return listener; } [MethodImpl ( MethodImplOptions.AggressiveInlining )] @@ -133,6 +143,9 @@ private void StartAccept ( int poolIndex ) { if (!_isListening) return; + if (Volatile.Read ( ref _listenerRestarting ) == 1) + return; + while (_isListening) { var args = _acceptArgsPool [ poolIndex ]; args.AcceptSocket = null; @@ -145,11 +158,17 @@ private void StartAccept ( int poolIndex ) { return; } catch (SocketException) { + if (Volatile.Read ( ref _listenerRestarting ) == 1) + return; + QueueStartAccept ( poolIndex, ListenerAcceptRetryDelayMilliseconds ); return; } int rearmDelayMs = ProcessAcceptInline ( args, poolIndex ); + if (rearmDelayMs < 0) + return; + if (rearmDelayMs > 0) { QueueStartAccept ( poolIndex, rearmDelayMs ); return; @@ -160,6 +179,9 @@ private void StartAccept ( int poolIndex ) { private void OnAcceptCompleted ( object? sender, SocketAsyncEventArgs e ) { int poolIndex = (int) e.UserToken!; int rearmDelayMs = ProcessAcceptInline ( e, poolIndex ); + if (rearmDelayMs < 0) + return; + if (rearmDelayMs > 0) QueueStartAccept ( poolIndex, rearmDelayMs ); else @@ -173,6 +195,11 @@ private int ProcessAcceptInline ( SocketAsyncEventArgs e, int poolIndex ) { e.AcceptSocket?.Dispose (); e.AcceptSocket = null; + if (IsListenerFatalError ( socketError )) { + TriggerListenerRebuild (); + return -1; + } + return IsConnectionAcceptNoise ( socketError ) ? 0 : ListenerAcceptRetryDelayMilliseconds; @@ -215,6 +242,46 @@ or SocketError.ConnectionReset or SocketError.ConnectionAborted or SocketError.NetworkReset; + private static bool IsListenerFatalError ( SocketError socketError ) => + socketError is SocketError.InvalidArgument + or SocketError.NotSocket + or SocketError.Shutdown + or SocketError.OperationAborted + or SocketError.Interrupted; + + private void TriggerListenerRebuild () { + if (Interlocked.CompareExchange ( ref _listenerRestarting, 1, 0 ) != 0) + return; + + _ = RebuildListenerAsync (); + } + + private async Task RebuildListenerAsync () { + try { _listener.Close (); } catch { } + try { _listener.Dispose (); } catch { } + + while (_isListening && !_disposedValue) { + await Task.Delay ( ListenerAcceptRetryDelayMilliseconds ).ConfigureAwait ( false ); + + try { + Socket newListener = CreateListenerSocket (); + newListener.Bind ( _endpoint ); + newListener.Listen ( backlog: 4096 ); + _listener = newListener; + break; + } + catch (SocketException) { + } + } + + Interlocked.Exchange ( ref _listenerRestarting, 0 ); + + if (_isListening && !_disposedValue) { + for (int i = 0; i < AcceptPoolSize; i++) + StartAccept ( i ); + } + } + [MethodImpl ( MethodImplOptions.AggressiveOptimization )] internal async Task ProcessConnectionCoreAsync ( Socket client ) { // Early exit se não há handler