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