Merge branch 'release/2.2'

This commit is contained in:
John Luo 2018-08-10 10:55:06 -07:00
commit 0010e971f2
9 changed files with 142 additions and 106 deletions

View File

@ -1131,7 +1131,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// chunked is applied to a response payload body, the sender MUST either // chunked is applied to a response payload body, the sender MUST either
// apply chunked as the final transfer coding or terminate the message // apply chunked as the final transfer coding or terminate the message
// by closing the connection. // by closing the connection.
if (hasTransferEncoding && if (hasTransferEncoding &&
HttpHeaders.GetFinalTransferCoding(responseHeaders.HeaderTransferEncoding) != TransferCoding.Chunked) HttpHeaders.GetFinalTransferCoding(responseHeaders.HeaderTransferEncoding) != TransferCoding.Chunked)
{ {
_keepAlive = false; _keepAlive = false;

View File

@ -79,6 +79,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private readonly object _stateLock = new object(); private readonly object _stateLock = new object();
private int _highestOpenedStreamId; private int _highestOpenedStreamId;
private Http2ConnectionState _state = Http2ConnectionState.Open; private Http2ConnectionState _state = Http2ConnectionState.Open;
private readonly TaskCompletionSource<object> _streamsCompleted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly ConcurrentDictionary<int, Http2Stream> _streams = new ConcurrentDictionary<int, Http2Stream>(); private readonly ConcurrentDictionary<int, Http2Stream> _streams = new ConcurrentDictionary<int, Http2Stream>();
@ -256,6 +257,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, errorCode); _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, errorCode);
UpdateState(Http2ConnectionState.Closed); UpdateState(Http2ConnectionState.Closed);
} }
if (_streams.IsEmpty)
{
_streamsCompleted.TrySetResult(null);
}
} }
// Ensure aborting each stream doesn't result in unnecessary WINDOW_UPDATE frames being sent. // Ensure aborting each stream doesn't result in unnecessary WINDOW_UPDATE frames being sent.
@ -266,6 +272,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
stream.Abort(connectionError); stream.Abort(connectionError);
} }
await _streamsCompleted.Task;
_frameWriter.Complete(); _frameWriter.Complete();
} }
catch catch
@ -891,13 +899,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
_streams.TryRemove(streamId, out _); _streams.TryRemove(streamId, out _);
if (_state == Http2ConnectionState.Closing && _streams.IsEmpty) if (_streams.IsEmpty)
{ {
_frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, Http2ErrorCode.NO_ERROR); if (_state == Http2ConnectionState.Closing)
UpdateState(Http2ConnectionState.Closed); {
_frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, Http2ErrorCode.NO_ERROR);
UpdateState(Http2ConnectionState.Closed);
// Wake up request processing loop so the connection can complete if there are no pending requests // Wake up request processing loop so the connection can complete if there are no pending requests
Input.CancelPendingRead(); Input.CancelPendingRead();
}
if (_state != Http2ConnectionState.Open)
{
// Complete the task waiting on all streams to finish
_streamsCompleted.TrySetResult(null);
}
} }
} }
} }

View File

@ -11,22 +11,20 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
{ {
public class SocketAwaitable : ICriticalNotifyCompletion public class SocketAwaitableEventArgs : SocketAsyncEventArgs, ICriticalNotifyCompletion
{ {
private static readonly Action _callbackCompleted = () => { }; private static readonly Action _callbackCompleted = () => { };
private readonly PipeScheduler _ioScheduler; private readonly PipeScheduler _ioScheduler;
private Action _callback; private Action _callback;
private int _bytesTransferred;
private SocketError _error;
public SocketAwaitable(PipeScheduler ioScheduler) public SocketAwaitableEventArgs(PipeScheduler ioScheduler)
{ {
_ioScheduler = ioScheduler; _ioScheduler = ioScheduler;
} }
public SocketAwaitable GetAwaiter() => this; public SocketAwaitableEventArgs GetAwaiter() => this;
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted); public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
public int GetResult() public int GetResult()
@ -35,12 +33,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
_callback = null; _callback = null;
if (_error != SocketError.Success) if (SocketError != SocketError.Success)
{ {
throw new SocketException((int)_error); ThrowSocketException(SocketError);
} }
return _bytesTransferred; return BytesTransferred;
void ThrowSocketException(SocketError e)
{
throw new SocketException((int)e);
}
} }
public void OnCompleted(Action continuation) public void OnCompleted(Action continuation)
@ -57,10 +60,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
OnCompleted(continuation); OnCompleted(continuation);
} }
public void Complete(int bytesTransferred, SocketError socketError) public void Complete()
{
OnCompleted(this);
}
protected override void OnCompleted(SocketAsyncEventArgs _)
{ {
_error = socketError;
_bytesTransferred = bytesTransferred;
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted);
if (continuation != null) if (continuation != null)

View File

@ -7,42 +7,29 @@ using System.Net.Sockets;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
{ {
public class SocketReceiver : IDisposable public sealed class SocketReceiver : SocketSenderReceiverBase
{ {
private readonly Socket _socket; public SocketReceiver(Socket socket, PipeScheduler scheduler) : base(socket, scheduler)
private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs();
private readonly SocketAwaitable _awaitable;
public SocketReceiver(Socket socket, PipeScheduler scheduler)
{ {
_socket = socket;
_awaitable = new SocketAwaitable(scheduler);
_eventArgs.UserToken = _awaitable;
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
} }
public SocketAwaitable ReceiveAsync(Memory<byte> buffer) public SocketAwaitableEventArgs ReceiveAsync(Memory<byte> buffer)
{ {
#if NETCOREAPP2_1 #if NETCOREAPP2_1
_eventArgs.SetBuffer(buffer); _awaitableEventArgs.SetBuffer(buffer);
#elif NETSTANDARD2_0 #elif NETSTANDARD2_0
var segment = buffer.GetArray(); var segment = buffer.GetArray();
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); _awaitableEventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
#else #else
#error TFMs need to be updated #error TFMs need to be updated
#endif #endif
if (!_socket.ReceiveAsync(_eventArgs)) if (!_socket.ReceiveAsync(_awaitableEventArgs))
{ {
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError); _awaitableEventArgs.Complete();
} }
return _awaitable; return _awaitableEventArgs;
}
public void Dispose()
{
_eventArgs.Dispose();
} }
} }
} }

View File

@ -11,23 +11,15 @@ using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
{ {
public class SocketSender : IDisposable public sealed class SocketSender : SocketSenderReceiverBase
{ {
private readonly Socket _socket;
private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs();
private readonly SocketAwaitable _awaitable;
private List<ArraySegment<byte>> _bufferList; private List<ArraySegment<byte>> _bufferList;
public SocketSender(Socket socket, PipeScheduler scheduler) public SocketSender(Socket socket, PipeScheduler scheduler) : base(socket, scheduler)
{ {
_socket = socket;
_awaitable = new SocketAwaitable(scheduler);
_eventArgs.UserToken = _awaitable;
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
} }
public SocketAwaitable SendAsync(ReadOnlySequence<byte> buffers) public SocketAwaitableEventArgs SendAsync(ReadOnlySequence<byte> buffers)
{ {
if (buffers.IsSingleSegment) if (buffers.IsSingleSegment)
{ {
@ -35,49 +27,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
} }
#if NETCOREAPP2_1 #if NETCOREAPP2_1
if (!_eventArgs.MemoryBuffer.Equals(Memory<byte>.Empty)) if (!_awaitableEventArgs.MemoryBuffer.Equals(Memory<byte>.Empty))
#elif NETSTANDARD2_0 #elif NETSTANDARD2_0
if (_eventArgs.Buffer != null) if (_awaitableEventArgs.Buffer != null)
#else #else
#error TFMs need to be updated #error TFMs need to be updated
#endif #endif
{ {
_eventArgs.SetBuffer(null, 0, 0); _awaitableEventArgs.SetBuffer(null, 0, 0);
} }
_eventArgs.BufferList = GetBufferList(buffers); _awaitableEventArgs.BufferList = GetBufferList(buffers);
if (!_socket.SendAsync(_eventArgs)) if (!_socket.SendAsync(_awaitableEventArgs))
{ {
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError); _awaitableEventArgs.Complete();
} }
return _awaitable; return _awaitableEventArgs;
} }
private SocketAwaitable SendAsync(ReadOnlyMemory<byte> memory) private SocketAwaitableEventArgs SendAsync(ReadOnlyMemory<byte> memory)
{ {
// The BufferList getter is much less expensive then the setter. // The BufferList getter is much less expensive then the setter.
if (_eventArgs.BufferList != null) if (_awaitableEventArgs.BufferList != null)
{ {
_eventArgs.BufferList = null; _awaitableEventArgs.BufferList = null;
} }
#if NETCOREAPP2_1 #if NETCOREAPP2_1
_eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory)); _awaitableEventArgs.SetBuffer(MemoryMarshal.AsMemory(memory));
#elif NETSTANDARD2_0 #elif NETSTANDARD2_0
var segment = memory.GetArray(); var segment = memory.GetArray();
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); _awaitableEventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
#else #else
#error TFMs need to be updated #error TFMs need to be updated
#endif #endif
if (!_socket.SendAsync(_eventArgs)) if (!_socket.SendAsync(_awaitableEventArgs))
{ {
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError); _awaitableEventArgs.Complete();
} }
return _awaitable; return _awaitableEventArgs;
} }
private List<ArraySegment<byte>> GetBufferList(ReadOnlySequence<byte> buffer) private List<ArraySegment<byte>> GetBufferList(ReadOnlySequence<byte> buffer)
@ -102,10 +94,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
return _bufferList; return _bufferList;
} }
public void Dispose()
{
_eventArgs.Dispose();
}
} }
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO.Pipelines;
using System.Net.Sockets;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
{
public abstract class SocketSenderReceiverBase : IDisposable
{
protected readonly Socket _socket;
protected readonly SocketAwaitableEventArgs _awaitableEventArgs;
protected SocketSenderReceiverBase(Socket socket, PipeScheduler scheduler)
{
_socket = socket;
_awaitableEventArgs = new SocketAwaitableEventArgs(scheduler);
}
public void Dispose() => _awaitableEventArgs.Dispose();
}
}

View File

@ -1065,12 +1065,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public async Task DATA_Received_NoStreamWindowSpace_ConnectionError() public async Task DATA_Received_NoStreamWindowSpace_ConnectionError()
{ {
// I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since
// the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to
// observe when all the blocks are returned. This can be removed after we implement graceful shutdown.
Dispose();
InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true));
// _maxData should be 1/4th of the default initial window size + 1. // _maxData should be 1/4th of the default initial window size + 1.
Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4); Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4);
@ -1093,12 +1087,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public async Task DATA_Received_NoConnectionWindowSpace_ConnectionError() public async Task DATA_Received_NoConnectionWindowSpace_ConnectionError()
{ {
// I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since
// the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to
// observe when all the blocks are returned. This can be removed after we implement graceful shutdown.
Dispose();
InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true));
// _maxData should be 1/4th of the default initial window size + 1. // _maxData should be 1/4th of the default initial window size + 1.
Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4); Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4);
@ -3286,8 +3274,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_pair.Application.Output.Complete(new ConnectionResetException(string.Empty)); _pair.Application.Output.Complete(new ConnectionResetException(string.Empty));
var result = await _pair.Application.Input.ReadAsync(); await StopConnectionAsync(1, ignoreNonGoAwayFrames: false);
Assert.True(result.IsCompleted);
Assert.Single(_logger.Messages, m => m.Exception is ConnectionResetException); Assert.Single(_logger.Messages, m => m.Exception is ConnectionResetException);
} }
@ -3337,6 +3324,54 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
VerifyGoAway(await ReceiveFrameAsync(), 0, Http2ErrorCode.NO_ERROR); VerifyGoAway(await ReceiveFrameAsync(), 0, Http2ErrorCode.NO_ERROR);
} }
[Fact]
public async Task StopProcessingNextRequestSendsGracefulGOAWAYAndWaitsForStreamsToComplete()
{
var task = Task.CompletedTask;
await InitializeConnectionAsync(context => task);
// Send and receive an unblocked request
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
// Send a blocked request
var tcs = new TaskCompletionSource<object>(TaskContinuationOptions.RunContinuationsAsynchronously);
task = tcs.Task;
await StartStreamAsync(3, _browserRequestHeaders, endStream: false);
// Close pipe
_pair.Application.Output.Complete();
// Assert connection closed
await _closedStateReached.Task.DefaultTimeout();
VerifyGoAway(await ReceiveFrameAsync(), 3, Http2ErrorCode.NO_ERROR);
// Assert connection shutdown is still blocked
// ProcessRequestsAsync completes the connection's Input pipe
var readTask = _pair.Application.Input.ReadAsync();
_pair.Application.Input.CancelPendingRead();
var result = await readTask;
Assert.False(result.IsCompleted);
// Unblock the request and ProcessRequestsAsync
tcs.TrySetResult(null);
await _connectionTask;
// Assert connection's Input pipe is completed
readTask = _pair.Application.Input.ReadAsync();
_pair.Application.Input.CancelPendingRead();
result = await readTask;
Assert.True(result.IsCompleted);
}
[Fact] [Fact]
public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhenAllStreamsComplete() public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhenAllStreamsComplete()
{ {

View File

@ -963,12 +963,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public async Task ContentLength_Received_SingleDataFrameUnderSize_Reset() public async Task ContentLength_Received_SingleDataFrameUnderSize_Reset()
{ {
// I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since
// the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to
// observe when all the blocks are returned. This can be removed after we implement graceful shutdown.
Dispose();
InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true));
var headers = new[] var headers = new[]
{ {
new KeyValuePair<string, string>(HeaderNames.Method, "POST"), new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
@ -996,12 +990,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public async Task ContentLength_Received_MultipleDataFramesOverSize_Reset() public async Task ContentLength_Received_MultipleDataFramesOverSize_Reset()
{ {
// I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since
// the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to
// observe when all the blocks are returned. This can be removed after we implement graceful shutdown.
Dispose();
InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true));
var headers = new[] var headers = new[]
{ {
new KeyValuePair<string, string>(HeaderNames.Method, "POST"), new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
@ -1032,12 +1020,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public async Task ContentLength_Received_MultipleDataFramesUnderSize_Reset() public async Task ContentLength_Received_MultipleDataFramesUnderSize_Reset()
{ {
// I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since
// the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to
// observe when all the blocks are returned. This can be removed after we implement graceful shutdown.
Dispose();
InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true));
var headers = new[] var headers = new[]
{ {
new KeyValuePair<string, string>(HeaderNames.Method, "POST"), new KeyValuePair<string, string>(HeaderNames.Method, "POST"),

View File

@ -27,9 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
[MemberData(nameof(H2SpecTestCases))] [MemberData(nameof(H2SpecTestCases))]
public async Task RunIndividualTestCase(H2SpecTestCase testCase) public async Task RunIndividualTestCase(H2SpecTestCase testCase)
{ {
var memoryPoolFactory = new DiagnosticMemoryPoolFactory(allowLateReturn: true); var hostBuilder = TransportSelector.GetWebHostBuilder()
var hostBuilder = TransportSelector.GetWebHostBuilder(memoryPoolFactory.Create)
.UseKestrel(options => .UseKestrel(options =>
{ {
options.Listen(IPAddress.Loopback, 0, listenOptions => options.Listen(IPAddress.Loopback, 0, listenOptions =>
@ -66,7 +64,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
{ {
skip = "https://github.com/aspnet/KestrelHttpServer/issues/2154"; skip = "https://github.com/aspnet/KestrelHttpServer/issues/2154";
} }
dataset.Add(new H2SpecTestCase() dataset.Add(new H2SpecTestCase()
{ {
Id = testcase.Item1, Id = testcase.Item1,
@ -74,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
Https = false, Https = false,
Skip = skip, Skip = skip,
}); });
dataset.Add(new H2SpecTestCase() dataset.Add(new H2SpecTestCase()
{ {
Id = testcase.Item1, Id = testcase.Item1,