Flow the cancellation tokens to ReadAsync and WriteAsync (#2865)
This commit is contained in:
parent
0256019979
commit
edc1935475
|
|
@ -155,6 +155,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// TryRead can throw OperationCanceledException https://github.com/dotnet/corefx/issues/32029
|
||||||
|
// beacuse of buggy logic, this works around that for now
|
||||||
|
}
|
||||||
catch (BadHttpRequestException ex)
|
catch (BadHttpRequestException ex)
|
||||||
{
|
{
|
||||||
// At this point, the response has already been written, so this won't result in a 4XX response;
|
// At this point, the response has already been written, so this won't result in a 4XX response;
|
||||||
|
|
|
||||||
|
|
@ -73,23 +73,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return WriteAsync(Constants.EmptyData, cancellationToken);
|
return WriteAsync(Constants.EmptyData, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write<T>(Func<PipeWriter, T, long> callback, T state)
|
public Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state, CancellationToken cancellationToken)
|
||||||
{
|
|
||||||
lock (_contextLock)
|
|
||||||
{
|
|
||||||
if (_completed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = _pipeWriter;
|
|
||||||
var bytesCommitted = callback(buffer, state);
|
|
||||||
_unflushedBytes += bytesCommitted;
|
|
||||||
_totalBytesCommitted += bytesCommitted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state)
|
|
||||||
{
|
{
|
||||||
lock (_contextLock)
|
lock (_contextLock)
|
||||||
{
|
{
|
||||||
|
|
@ -104,7 +88,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
_totalBytesCommitted += bytesCommitted;
|
_totalBytesCommitted += bytesCommitted;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FlushAsync();
|
return FlushAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteResponseHeaders(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders)
|
public void WriteResponseHeaders(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders)
|
||||||
|
|
|
||||||
|
|
@ -915,7 +915,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
private Task WriteChunkedAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
|
private Task WriteChunkedAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Output.WriteAsync(_writeChunk, data);
|
return Output.WriteAsync(_writeChunk, data, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long WriteChunk(PipeWriter writableBuffer, ReadOnlyMemory<byte> buffer)
|
private static long WriteChunk(PipeWriter writableBuffer, ReadOnlyMemory<byte> buffer)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
public interface IHttpOutputProducer : IDisposable
|
public interface IHttpOutputProducer : IDisposable
|
||||||
{
|
{
|
||||||
void Abort(ConnectionAbortedException abortReason);
|
void Abort(ConnectionAbortedException abortReason);
|
||||||
Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state);
|
Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state, CancellationToken cancellationToken);
|
||||||
Task FlushAsync(CancellationToken cancellationToken);
|
Task FlushAsync(CancellationToken cancellationToken);
|
||||||
Task Write100ContinueAsync();
|
Task Write100ContinueAsync();
|
||||||
void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders);
|
void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders);
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var result = await _context.RequestBodyPipe.Reader.ReadAsync();
|
var result = await _context.RequestBodyPipe.Reader.ReadAsync(cancellationToken);
|
||||||
var readableBuffer = result.Buffer;
|
var readableBuffer = result.Buffer;
|
||||||
var consumed = readableBuffer.End;
|
var consumed = readableBuffer.End;
|
||||||
var actual = 0;
|
var actual = 0;
|
||||||
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var result = await _context.RequestBodyPipe.Reader.ReadAsync();
|
var result = await _context.RequestBodyPipe.Reader.ReadAsync(cancellationToken);
|
||||||
var readableBuffer = result.Buffer;
|
var readableBuffer = result.Buffer;
|
||||||
var consumed = readableBuffer.End;
|
var consumed = readableBuffer.End;
|
||||||
var bytesRead = 0;
|
var bytesRead = 0;
|
||||||
|
|
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
bytesRead += memory.Length;
|
bytesRead += memory.Length;
|
||||||
|
|
||||||
#if NETCOREAPP2_1
|
#if NETCOREAPP2_1
|
||||||
await destination.WriteAsync(memory);
|
await destination.WriteAsync(memory, cancellationToken);
|
||||||
#elif NETSTANDARD2_0
|
#elif NETSTANDARD2_0
|
||||||
var array = memory.GetArray();
|
var array = memory.GetArray();
|
||||||
await destination.WriteAsync(array.Array, array.Offset, array.Count, cancellationToken);
|
await destination.WriteAsync(array.Array, array.Offset, array.Count, cancellationToken);
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state)
|
public Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
|
|
@ -31,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void WritesNoopAfterConnectionCloses()
|
public async Task WritesNoopAfterConnectionCloses()
|
||||||
{
|
{
|
||||||
var pipeOptions = new PipeOptions
|
var pipeOptions = new PipeOptions
|
||||||
(
|
(
|
||||||
|
|
@ -48,12 +49,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
var called = false;
|
var called = false;
|
||||||
|
|
||||||
socketOutput.Write((buffer, state) =>
|
await socketOutput.WriteAsync((buffer, state) =>
|
||||||
{
|
{
|
||||||
called = true;
|
called = true;
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
0);
|
0,
|
||||||
|
default);
|
||||||
|
|
||||||
Assert.False(called);
|
Assert.False(called);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
@ -55,6 +56,73 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RequestBodyReadAsyncCanBeCancelled()
|
||||||
|
{
|
||||||
|
var helloTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
using (var server = new TestServer(async context =>
|
||||||
|
{
|
||||||
|
var buffer = new byte[1024];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
int read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
||||||
|
|
||||||
|
Assert.Equal("Hello ", Encoding.UTF8.GetString(buffer, 0, read));
|
||||||
|
|
||||||
|
helloTcs.TrySetResult(null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// This shouldn't fail
|
||||||
|
helloTcs.TrySetException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await context.Request.Body.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
||||||
|
|
||||||
|
context.Response.ContentLength = 12;
|
||||||
|
await context.Response.WriteAsync("Read success");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
context.Response.ContentLength = 14;
|
||||||
|
await context.Response.WriteAsync("Read cancelled");
|
||||||
|
}
|
||||||
|
|
||||||
|
}, new TestServiceContext(LoggerFactory)))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"POST / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"Connection: keep-alive",
|
||||||
|
"Content-Length: 11",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await connection.Send("Hello ");
|
||||||
|
|
||||||
|
await helloTcs.Task;
|
||||||
|
|
||||||
|
// Cancel the body after hello is read
|
||||||
|
cts.Cancel();
|
||||||
|
|
||||||
|
await connection.Send("World");
|
||||||
|
|
||||||
|
await connection.Receive($"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 14",
|
||||||
|
"",
|
||||||
|
"Read cancelled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanUpgradeRequestWithConnectionKeepAliveUpgradeHeader()
|
public async Task CanUpgradeRequestWithConnectionKeepAliveUpgradeHeader()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
@ -99,6 +100,61 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResponseBodyWriteAsyncCanBeCancelled()
|
||||||
|
{
|
||||||
|
var serviceContext = new TestServiceContext(LoggerFactory);
|
||||||
|
serviceContext.ServerOptions.Limits.MaxResponseBufferSize = 5;
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
var appTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var writeReturnedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
using (var server = new TestServer(async context =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("hello", cts.Token).DefaultTimeout();
|
||||||
|
writeReturnedTcs.TrySetResult(null);
|
||||||
|
|
||||||
|
var task = context.Response.WriteAsync("world", cts.Token);
|
||||||
|
Assert.False(task.IsCompleted);
|
||||||
|
await task.DefaultTimeout();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
appTcs.TrySetException(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
appTcs.TrySetResult(null);
|
||||||
|
writeReturnedTcs.TrySetCanceled();
|
||||||
|
}
|
||||||
|
}, serviceContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await connection.Receive($"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
"5",
|
||||||
|
"hello");
|
||||||
|
|
||||||
|
await writeReturnedTcs.Task.DefaultTimeout();
|
||||||
|
|
||||||
|
cts.Cancel();
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<OperationCanceledException>(() => appTcs.Task).DefaultTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public Task ResponseStatusCodeSetBeforeHttpContextDisposeAppException()
|
public Task ResponseStatusCodeSetBeforeHttpContextDisposeAppException()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -658,7 +658,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await readTcs.Task);
|
await Assert.ThrowsAsync<TaskCanceledException>(async () => await readTcs.Task);
|
||||||
|
|
||||||
// The cancellation token for only the last request should be triggered.
|
// The cancellation token for only the last request should be triggered.
|
||||||
var abortedRequestId = await registrationTcs.Task;
|
var abortedRequestId = await registrationTcs.Task.DefaultTimeout();
|
||||||
Assert.Equal(2, abortedRequestId);
|
Assert.Equal(2, abortedRequestId);
|
||||||
|
|
||||||
Assert.Single(TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel" &&
|
Assert.Single(TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel" &&
|
||||||
|
|
|
||||||
|
|
@ -303,12 +303,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
|
||||||
Assert.NotEmpty(completeQueue);
|
Assert.NotEmpty(completeQueue);
|
||||||
|
|
||||||
// Add more bytes to the write-behind buffer to prevent the next write from
|
// Add more bytes to the write-behind buffer to prevent the next write from
|
||||||
outputProducer.Write((writableBuffer, state) =>
|
_ = outputProducer.WriteAsync((writableBuffer, state) =>
|
||||||
{
|
{
|
||||||
writableBuffer.Write(state);
|
writableBuffer.Write(state);
|
||||||
return state.Count;
|
return state.Count;
|
||||||
},
|
},
|
||||||
halfWriteBehindBuffer);
|
halfWriteBehindBuffer,
|
||||||
|
default);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var writeTask2 = outputProducer.WriteDataAsync(halfWriteBehindBuffer);
|
var writeTask2 = outputProducer.WriteDataAsync(halfWriteBehindBuffer);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue