From 776c6d0468089a4a3f1b558c10e31b0bccc5917d Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 17 Aug 2021 10:13:02 -0700 Subject: [PATCH 1/2] Cancel the token when it's created after an Abort/Disconnect #17278 --- .../src/RequestProcessing/RequestContext.cs | 6 +- .../test/FunctionalTests/RequestTests.cs | 95 +++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index 321784dfe7e4..e3ef94d2552b 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -63,7 +63,7 @@ public CancellationToken DisconnectToken } else { - _disconnectToken = CancellationToken.None; + _disconnectToken = new CancellationToken(canceled: true); } } } @@ -181,6 +181,10 @@ public void Abort() } _requestAbortSource.Dispose(); } + else + { + _disconnectToken = new CancellationToken(canceled: true); + } ForceCancelRequest(); Request.Dispose(); // Only Abort, Response.Dispose() tries a graceful flush diff --git a/src/Servers/HttpSys/test/FunctionalTests/RequestTests.cs b/src/Servers/HttpSys/test/FunctionalTests/RequestTests.cs index def6ce32c693..b40df9a02f41 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/RequestTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/RequestTests.cs @@ -468,6 +468,101 @@ public async Task Request_EscapedControlCharacters_400() } } + [ConditionalFact] + public async Task RequestAborted_AfterAccessingProperty_Notified() + { + var registered = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var result = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var server = Utilities.CreateHttpServerReturnRoot("/", out var address, async httpContext => + { + var ct = httpContext.RequestAborted; + + if (!ct.CanBeCanceled || ct.IsCancellationRequested) + { + result.SetException(new Exception("The CT isn't valid.")); + return; + } + + ct.Register(() => result.SetResult()); + + registered.SetResult(); + + // Don't exit until it fires or else it could be disposed. + await result.Task.DefaultTimeout(); + }); + + // Send a request and then abort. + + var uri = new Uri(address); + StringBuilder builder = new StringBuilder(); + builder.AppendLine("POST / HTTP/1.1"); + builder.AppendLine("Connection: close"); + builder.AppendLine("Content-Length: 10"); + builder.Append("HOST: "); + builder.AppendLine(uri.Authority); + builder.AppendLine(); + + byte[] request = Encoding.ASCII.GetBytes(builder.ToString()); + + using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + + await socket.ConnectAsync(uri.Host, uri.Port); + socket.Send(request); + + // Wait for the token to be setup before aborting. + await registered.Task.DefaultTimeout(); + + socket.Close(); + + await result.Task.DefaultTimeout(); + } + + [ConditionalFact] + public async Task RequestAbortedDurringRead_BeforeAccessingProperty_TokenAlreadyCanceled() + { + var requestAborted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var result = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var server = Utilities.CreateHttpServerReturnRoot("/", out var address, async httpContext => + { + await requestAborted.Task.DefaultTimeout(); + try + { + await httpContext.Request.Body.ReadAsync(new byte[10]).DefaultTimeout(); + result.SetException(new Exception("This should have aborted")); + return; + } + catch (IOException) + { + } + + result.SetResult(httpContext.RequestAborted.IsCancellationRequested); + }); + + // Send a request and then abort. + + var uri = new Uri(address); + StringBuilder builder = new StringBuilder(); + builder.AppendLine("POST / HTTP/1.1"); + builder.AppendLine("Connection: close"); + builder.AppendLine("Content-Length: 10"); + builder.Append("HOST: "); + builder.AppendLine(uri.Authority); + builder.AppendLine(); + + byte[] request = Encoding.ASCII.GetBytes(builder.ToString()); + + using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + + await socket.ConnectAsync(uri.Host, uri.Port); + socket.Send(request); + socket.Close(); + + requestAborted.SetResult(); + + var wasCancelled = await result.Task; + Assert.True(wasCancelled); + } + private IServer CreateServer(out string root, RequestDelegate app) { // TODO: We're just doing this to get a dynamic port. This can be removed later when we add support for hot-adding prefixes. From 2ee2b8eace814635feb85ab2468012ba7082a724 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Fri, 20 Aug 2021 09:34:16 -0700 Subject: [PATCH 2/2] Update src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs --- src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index e3ef94d2552b..fabae04d35e5 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -63,7 +63,7 @@ public CancellationToken DisconnectToken } else { - _disconnectToken = new CancellationToken(canceled: true); + _disconnectToken = CancellationToken.None; } } }