Skip to content

Cancel the token when it's created after an Abort/Disconnect #35418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
95 changes: 95 additions & 0 deletions src/Servers/HttpSys/test/FunctionalTests/RequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>(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.
Expand Down