Skip to content

Commit 5a04636

Browse files
authored
Fix the HttpSys client disconnect race condition #12194 (#35401)
1 parent 45d2c24 commit 5a04636

File tree

4 files changed

+86
-6
lines changed

4 files changed

+86
-6
lines changed

src/Servers/HttpSys/HttpSysServer.slnf

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"path": "..\\..\\..\\AspNetCore.sln",
44
"projects": [
55
"src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj",
6+
"src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
67
"src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
78
"src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
89
"src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
@@ -11,12 +12,18 @@
1112
"src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
1213
"src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj",
1314
"src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj",
14-
"src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
1515
"src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj",
1616
"src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj",
1717
"src\\Http\\Metadata\\src\\Microsoft.AspNetCore.Metadata.csproj",
18+
"src\\Http\\Routing.Abstractions\\src\\Microsoft.AspNetCore.Routing.Abstractions.csproj",
19+
"src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj",
1820
"src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj",
21+
"src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj",
22+
"src\\Middleware\\Diagnostics\\src\\Microsoft.AspNetCore.Diagnostics.csproj",
1923
"src\\Middleware\\HostFiltering\\src\\Microsoft.AspNetCore.HostFiltering.csproj",
24+
"src\\Middleware\\HttpOverrides\\src\\Microsoft.AspNetCore.HttpOverrides.csproj",
25+
"src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj",
26+
"src\\Security\\Authorization\\Core\\src\\Microsoft.AspNetCore.Authorization.csproj",
2027
"src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
2128
"src\\Servers\\HttpSys\\samples\\HotAddSample\\HotAddSample.csproj",
2229
"src\\Servers\\HttpSys\\samples\\QueueSharing\\QueueSharing.csproj",
@@ -25,7 +32,11 @@
2532
"src\\Servers\\HttpSys\\src\\Microsoft.AspNetCore.Server.HttpSys.csproj",
2633
"src\\Servers\\HttpSys\\test\\FunctionalTests\\Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj",
2734
"src\\Servers\\HttpSys\\test\\Tests\\Microsoft.AspNetCore.Server.HttpSys.Tests.csproj",
35+
"src\\Servers\\IIS\\IISIntegration\\src\\Microsoft.AspNetCore.Server.IISIntegration.csproj",
2836
"src\\Servers\\IIS\\IIS\\src\\Microsoft.AspNetCore.Server.IIS.csproj",
37+
"src\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj",
38+
"src\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj",
39+
"src\\Servers\\Kestrel\\Transport.Quic\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj",
2940
"src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj",
3041
"src\\Testing\\src\\Microsoft.AspNetCore.Testing.csproj"
3142
]

src/Servers/HttpSys/src/RequestProcessing/RequestStreamAsyncResult.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ internal void Complete(int read, uint errorCode = UnsafeNclNativeMethods.ErrorCo
135135

136136
internal void Fail(Exception ex)
137137
{
138+
// Make sure the Abort state is set before signaling the callback so we can avoid race condtions with user code.
139+
Dispose();
140+
_requestStream.Abort();
138141
if (_tcs.TrySetException(ex) && _callback != null)
139142
{
140143
try
@@ -147,8 +150,6 @@ internal void Fail(Exception ex)
147150
// TODO: Log
148151
}
149152
}
150-
Dispose();
151-
_requestStream.Abort();
152153
}
153154

154155
[SuppressMessage("Microsoft.Usage", "CA2216:DisposableTypesShouldDeclareFinalizer", Justification = "The disposable resource referenced does have a finalizer.")]

src/Servers/HttpSys/src/RequestProcessing/Response.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ internal sealed class Response
2626
private static bool SupportsGoAway = true;
2727

2828
private ResponseState _responseState;
29+
private bool _aborted;
2930
private string? _reasonPhrase;
3031
private ResponseBody? _nativeStream;
3132
private AuthenticationSchemes _authChallenges;
@@ -196,14 +197,16 @@ internal void MakeTrailersReadOnly()
196197

197198
internal void Abort()
198199
{
199-
// Update state for HasStarted. Do not attempt a graceful Dispose.
200-
_responseState = ResponseState.Closed;
200+
// Do not attempt a graceful Dispose.
201+
// _responseState is not modified because that refers to app state like modifying
202+
// status and headers. See https://github.com/dotnet/aspnetcore/issues/12194.
203+
_aborted = true;
201204
}
202205

203206
// should only be called from RequestContext
204207
internal void Dispose()
205208
{
206-
if (_responseState >= ResponseState.Closed)
209+
if (_aborted || _responseState >= ResponseState.Closed)
207210
{
208211
return;
209212
}

src/Servers/HttpSys/test/FunctionalTests/ResponseTests.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
using System.IO;
66
using System.Net;
77
using System.Net.Http;
8+
using System.Net.Sockets;
9+
using System.Text;
810
using System.Threading;
911
using System.Threading.Tasks;
12+
using Microsoft.AspNetCore.Http;
1013
using Microsoft.AspNetCore.Http.Features;
1114
using Microsoft.AspNetCore.Testing;
1215
using Xunit;
@@ -210,6 +213,68 @@ public async Task Response_OnStartingThrowsAfterWrite_WriteThrowsAndStillCallsOn
210213
}
211214
}
212215

216+
[ConditionalFact]
217+
public async Task ClientDisconnectsBeforeResponse_ResponseCanStillBeModified()
218+
{
219+
var readStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
220+
var readCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
221+
using var server = Utilities.CreateHttpServer(out var address, async httpContext =>
222+
{
223+
var readTask = httpContext.Request.Body.ReadAsync(new byte[10]);
224+
readStarted.SetResult();
225+
try
226+
{
227+
await readTask;
228+
readCompleted.SetException(new InvalidOperationException("The read wasn't supposed to succeed"));
229+
return;
230+
}
231+
catch (IOException)
232+
{
233+
}
234+
235+
try
236+
{
237+
// https://github.com/dotnet/aspnetcore/issues/12194
238+
// Modifying the response after the client has disconnected must be allowed.
239+
Assert.False(httpContext.Response.HasStarted);
240+
httpContext.Response.StatusCode = 400;
241+
httpContext.Response.ContentType = "text/plain";
242+
await httpContext.Response.WriteAsync("Body");
243+
}
244+
catch (Exception ex)
245+
{
246+
readCompleted.SetException(ex);
247+
return;
248+
}
249+
250+
readCompleted.SetResult();
251+
});
252+
253+
// Send a request without the body.
254+
var uri = new Uri(address);
255+
StringBuilder builder = new StringBuilder();
256+
builder.AppendLine("POST / HTTP/1.1");
257+
builder.AppendLine("Connection: close");
258+
builder.Append("HOST: ");
259+
builder.AppendLine(uri.Authority);
260+
builder.AppendLine("Content-Length: 10");
261+
builder.AppendLine();
262+
263+
byte[] request = Encoding.ASCII.GetBytes(builder.ToString());
264+
265+
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
266+
socket.Connect(uri.Host, uri.Port);
267+
socket.Send(request);
268+
269+
await readStarted.Task.DefaultTimeout();
270+
271+
// Disconnect
272+
socket.Close();
273+
274+
// Make sure the server code behaved as expected.
275+
await readCompleted.Task.DefaultTimeout();
276+
}
277+
213278
private async Task<HttpResponseMessage> SendRequestAsync(string uri)
214279
{
215280
using (var client = new HttpClient())

0 commit comments

Comments
 (0)