Skip to content

Commit 5a4c82e

Browse files
amcaseyadityamandaleeka
authored andcommitted
Merged PR 32090: [7.0] Forcibly close socket on abort
Forcibly close socket when connection is aborted.
1 parent 5b4c366 commit 5a4c82e

File tree

15 files changed

+443
-64
lines changed

15 files changed

+443
-64
lines changed

src/Servers/IIS/IIS/test/Common.LongTests/ShutdownTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,8 +535,15 @@ public async Task ClosesConnectionOnServerAbortOutOfProcess()
535535
var response = await deploymentResult.HttpClient.GetAsync("/Abort").TimeoutAfter(TimeoutExtensions.DefaultTimeoutValue);
536536

537537
Assert.Equal(HttpStatusCode.BadGateway, response.StatusCode);
538+
539+
#if NEWSHIM_FUNCTIONALS
540+
// In-proc SocketConnection isn't used and there's no abort
538541
// 0x80072f78 ERROR_HTTP_INVALID_SERVER_RESPONSE The server returned an invalid or unrecognized response
539542
Assert.Contains("0x80072f78", await response.Content.ReadAsStringAsync());
543+
#else
544+
// 0x80072efe ERROR_INTERNET_CONNECTION_ABORTED The connection with the server was terminated abnormally
545+
Assert.Contains("0x80072efe", await response.Content.ReadAsStringAsync());
546+
#endif
540547
}
541548
catch (HttpRequestException)
542549
{

src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,14 @@ protected override void OnRequestProcessingEnded()
9898
_http1Output.Dispose();
9999
}
100100

101-
public void OnInputOrOutputCompleted()
101+
void IRequestProcessor.OnInputOrOutputCompleted()
102+
{
103+
// Closed gracefully.
104+
_http1Output.Abort(ServerOptions.FinOnError ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!);
105+
CancelRequestAbortedToken();
106+
}
107+
108+
void IHttpOutputAborter.OnInputOrOutputCompleted()
102109
{
103110
_http1Output.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient));
104111
CancelRequestAbortedToken();

src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ protected void ThrowUnexpectedEndOfRequestContent()
204204
// so we call OnInputOrOutputCompleted() now to prevent a race in our tests where a 400
205205
// response is written after observing the unexpected end of request content instead of just
206206
// closing the connection without a response as expected.
207-
_context.OnInputOrOutputCompleted();
207+
((IHttpOutputAborter)_context).OnInputOrOutputCompleted();
208208

209209
KestrelBadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent);
210210
}

src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ public Http2Connection(HttpConnectionContext context)
159159
public void OnInputOrOutputCompleted()
160160
{
161161
TryClose();
162-
_frameWriter.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient));
162+
var useException = _context.ServiceContext.ServerOptions.FinOnError || _clientActiveStreamCount != 0;
163+
_frameWriter.Abort(useException ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!);
163164
}
164165

165166
public void Abort(ConnectionAbortedException ex)

src/Servers/Kestrel/Core/src/KestrelServerOptions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core;
2626
public class KestrelServerOptions
2727
{
2828
internal const string DisableHttp1LineFeedTerminatorsSwitchKey = "Microsoft.AspNetCore.Server.Kestrel.DisableHttp1LineFeedTerminators";
29+
private const string FinOnErrorSwitch = "Microsoft.AspNetCore.Server.Kestrel.FinOnError";
30+
private static readonly bool _finOnError;
31+
32+
static KestrelServerOptions()
33+
{
34+
AppContext.TryGetSwitch(FinOnErrorSwitch, out _finOnError);
35+
}
2936

3037
// internal to fast-path header decoding when RequestHeaderEncodingSelector is unchanged.
3138
internal static readonly Func<string, Encoding?> DefaultHeaderEncodingSelector = _ => null;
3239

40+
// Opt-out flag for back compat. Remove in 9.0 (or make public).
41+
internal bool FinOnError { get; set; } = _finOnError;
42+
3343
private Func<string, Encoding?> _requestHeaderEncodingSelector = DefaultHeaderEncodingSelector;
3444

3545
private Func<string, Encoding?> _responseHeaderEncodingSelector = DefaultHeaderEncodingSelector;

src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ internal sealed partial class SocketConnection : TransportConnection
3030
private readonly TaskCompletionSource _waitForConnectionClosedTcs = new TaskCompletionSource();
3131
private bool _connectionClosed;
3232
private readonly bool _waitForData;
33+
private readonly bool _finOnError;
3334

3435
internal SocketConnection(Socket socket,
3536
MemoryPool<byte> memoryPool,
@@ -38,7 +39,8 @@ internal SocketConnection(Socket socket,
3839
SocketSenderPool socketSenderPool,
3940
PipeOptions inputOptions,
4041
PipeOptions outputOptions,
41-
bool waitForData = true)
42+
bool waitForData = true,
43+
bool finOnError = false)
4244
{
4345
Debug.Assert(socket != null);
4446
Debug.Assert(memoryPool != null);
@@ -49,6 +51,7 @@ internal SocketConnection(Socket socket,
4951
_logger = logger;
5052
_waitForData = waitForData;
5153
_socketSenderPool = socketSenderPool;
54+
_finOnError = finOnError;
5255

5356
LocalEndPoint = _socket.LocalEndPoint;
5457
RemoteEndPoint = _socket.RemoteEndPoint;
@@ -380,11 +383,21 @@ private void Shutdown(Exception? shutdownReason)
380383
// ever observe the nondescript ConnectionAbortedException except for connection middleware attempting
381384
// to half close the connection which is currently unsupported.
382385
_shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Socket transport's send loop completed gracefully.");
386+
387+
// NB: not _shutdownReason since we don't want to do this on graceful completion
388+
if (!_finOnError && shutdownReason is not null)
389+
{
390+
SocketsLog.ConnectionWriteRst(_logger, this, shutdownReason.Message);
391+
392+
// This forces an abortive close with linger time 0 (and implies Dispose)
393+
_socket.Close(timeout: 0);
394+
return;
395+
}
396+
383397
SocketsLog.ConnectionWriteFin(_logger, this, _shutdownReason.Message);
384398

385399
try
386400
{
387-
// Try to gracefully close the socket even for aborts to match libuv behavior.
388401
_socket.Shutdown(SocketShutdown.Both);
389402
}
390403
catch

src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketsLog.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ public static void ConnectionWriteFin(ILogger logger, SocketConnection connectio
3131
}
3232
}
3333

34+
[LoggerMessage(8, LogLevel.Debug, @"Connection id ""{ConnectionId}"" sending RST because: ""{Reason}""", EventName = "ConnectionWriteRst", SkipEnabledCheck = true)]
35+
private static partial void ConnectionWriteRstCore(ILogger logger, string connectionId, string reason);
36+
37+
public static void ConnectionWriteRst(ILogger logger, SocketConnection connection, string reason)
38+
{
39+
if (logger.IsEnabled(LogLevel.Debug))
40+
{
41+
ConnectionWriteRstCore(logger, connection.ConnectionId, reason);
42+
}
43+
}
44+
3445
// Reserved: Event ID 11, EventName = ConnectionWrite
3546

3647
// Reserved: Event ID 12, EventName = ConnectionWriteCallback

src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionContextFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ public ConnectionContext Create(Socket socket)
113113
setting.SocketSenderPool,
114114
setting.InputOptions,
115115
setting.OutputOptions,
116-
waitForData: _options.WaitForDataBeforeAllocatingBuffer);
116+
waitForData: _options.WaitForDataBeforeAllocatingBuffer,
117+
finOnError: _options.FinOnError);
117118

118119
connection.Start();
119120
return connection;

src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionFactoryOptions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ internal SocketConnectionFactoryOptions(SocketTransportOptions transportOptions)
2323
MaxWriteBufferSize = transportOptions.MaxWriteBufferSize;
2424
UnsafePreferInlineScheduling = transportOptions.UnsafePreferInlineScheduling;
2525
MemoryPoolFactory = transportOptions.MemoryPoolFactory;
26+
FinOnError = transportOptions.FinOnError;
2627
}
2728

29+
// Opt-out flag for back compat. Remove in 9.0 (or make public).
30+
internal bool FinOnError { get; set; }
31+
2832
/// <summary>
2933
/// The number of I/O queues used to process requests. Set to 0 to directly schedule I/O to the ThreadPool.
3034
/// </summary>

src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
1313
/// </summary>
1414
public class SocketTransportOptions
1515
{
16+
private const string FinOnErrorSwitch = "Microsoft.AspNetCore.Server.Kestrel.FinOnError";
17+
private static readonly bool _finOnError;
18+
19+
static SocketTransportOptions()
20+
{
21+
AppContext.TryGetSwitch(FinOnErrorSwitch, out _finOnError);
22+
}
23+
24+
// Opt-out flag for back compat. Remove in 9.0 (or make public).
25+
internal bool FinOnError { get; set; } = _finOnError;
26+
1627
/// <summary>
1728
/// The number of I/O queues used to process requests. Set to 0 to directly schedule I/O to the ThreadPool.
1829
/// </summary>

0 commit comments

Comments
 (0)