diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/SocketOutput.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/SocketOutput.cs index d3064e27e9..8a3f9e5890 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/SocketOutput.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/SocketOutput.cs @@ -15,7 +15,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http public class SocketOutput : ISocketOutput { private const int _maxPendingWrites = 3; - private const int _maxBytesPreCompleted = 65536; // Well behaved WriteAsync users should await returned task, so there is no need to allocate more per connection by default private const int _initialTaskQueues = 1; private const int _maxPooledWriteContexts = 32; @@ -26,6 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http private readonly KestrelThread _thread; private readonly UvStreamHandle _socket; private readonly Connection _connection; + private readonly long? _maxBytesPreCompleted; private readonly string _connectionId; private readonly IKestrelTrace _log; private readonly IThreadPool _threadPool; @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http private bool _postingWrite = false; private bool _cancelled = false; - private int _numBytesPreCompleted = 0; + private long _numBytesPreCompleted = 0; private Exception _lastWriteError; private WriteContext _nextWriteContext; private readonly Queue _tasksPending; @@ -75,6 +75,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http _tasksPending = new Queue(_initialTaskQueues); _writeContextPool = new Queue(_maxPooledWriteContexts); _writeReqPool = thread.WriteReqPool; + _maxBytesPreCompleted = connection.ServerOptions.Limits.MaxResponseBufferSize; _head = thread.Memory.Lease(); _tail = _head; @@ -146,9 +147,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http _nextWriteContext.SocketDisconnect = true; } - if (_lastWriteError == null && - _tasksPending.Count == 0 && - _numBytesPreCompleted + buffer.Count <= _maxBytesPreCompleted) + if (!_maxBytesPreCompleted.HasValue || _numBytesPreCompleted + buffer.Count <= _maxBytesPreCompleted.Value) { // Complete the write task immediately if all previous write tasks have been completed, // the buffers haven't grown too large, and the last write to the socket succeeded. @@ -403,7 +402,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http } } - private void CompleteNextWrite(ref int bytesLeftToBuffer) + private void CompleteNextWrite(ref long bytesLeftToBuffer) { // Called inside _contextLock var waitingTask = _tasksPending.Dequeue(); @@ -441,10 +440,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http private void CompleteFinishedWrites(int status) { + if (!_maxBytesPreCompleted.HasValue) + { + Debug.Assert(_tasksPending.Count == 0); + return; + } + // Called inside _contextLock // bytesLeftToBuffer can be greater than _maxBytesPreCompleted // This allows large writes to complete once they've actually finished. - var bytesLeftToBuffer = _maxBytesPreCompleted - _numBytesPreCompleted; + var bytesLeftToBuffer = _maxBytesPreCompleted.Value - _numBytesPreCompleted; while (_tasksPending.Count > 0 && (_tasksPending.Peek().BytesToWrite) <= bytesLeftToBuffer) { @@ -454,8 +459,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http private void CompleteAllWrites() { + if (!_maxBytesPreCompleted.HasValue) + { + Debug.Assert(_tasksPending.Count == 0); + return; + } + // Called inside _contextLock - var bytesLeftToBuffer = _maxBytesPreCompleted - _numBytesPreCompleted; + var bytesLeftToBuffer = _maxBytesPreCompleted.Value - _numBytesPreCompleted; while (_tasksPending.Count > 0) { CompleteNextWrite(ref bytesLeftToBuffer); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServerLimits.cs b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServerLimits.cs index d59bcd75e9..6cfac04635 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServerLimits.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServerLimits.cs @@ -7,6 +7,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel { public class KestrelServerLimits { + // Matches the non-configurable default response buffer size for Kestrel in 1.0.0 + private long? _maxResponseBufferSize = 64 * 1024; + // Matches the default client_max_body_size in nginx. Also large enough that most requests // should be under the limit. private long? _maxRequestBufferSize = 1024 * 1024; @@ -20,6 +23,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel // Matches the default LimitRequestFields in Apache httpd. private int _maxRequestHeaderCount = 100; + /// + /// Gets or sets the maximum size of the response buffer before write + /// calls begin to block or return tasks that don't complete until the + /// buffer size drops below the configured limit. + /// + /// + /// When set to null, the size of the response buffer is unlimited. + /// When set to zero, all write calls will block or return tasks that + /// don't complete until the entire response buffer is flushed. + /// Defaults to 65,536 bytes (64 KB). + /// + public long? MaxResponseBufferSize + { + get + { + return _maxResponseBufferSize; + } + set + { + if (value.HasValue && value.Value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "Value must be null or a non-negative integer."); + } + _maxResponseBufferSize = value; + } + } + /// /// Gets or sets the maximum size of the request buffer. /// diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/KestrelServerLimitsTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/KestrelServerLimitsTests.cs index 111f97193c..5199a16941 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/KestrelServerLimitsTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/KestrelServerLimitsTests.cs @@ -9,6 +9,35 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { public class KestrelServerLimitsTests { + [Fact] + public void MaxResponseBufferSizeDefault() + { + Assert.Equal(64 * 1024, (new KestrelServerLimits()).MaxResponseBufferSize); + } + + [Theory] + [InlineData((long)-1)] + [InlineData(long.MinValue)] + public void MaxResponseBufferSizeInvalid(long value) + { + Assert.Throws(() => + { + (new KestrelServerLimits()).MaxResponseBufferSize = value; + }); + } + + [Theory] + [InlineData(null)] + [InlineData((long)0)] + [InlineData((long)1)] + [InlineData(long.MaxValue)] + public void MaxResponseBufferSizeValid(long? value) + { + var o = new KestrelServerLimits(); + o.MaxResponseBufferSize = value; + Assert.Equal(value, o.MaxResponseBufferSize); + } + [Fact] public void MaxRequestBufferSizeDefault() { diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/SocketOutputTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/SocketOutputTests.cs index 2f16107a8a..31ae21dbe6 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/SocketOutputTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/SocketOutputTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel.Internal; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; @@ -15,8 +16,43 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { public class SocketOutputTests { - [Fact] - public async Task CanWrite1MB() + public static TheoryData MaxResponseBufferSizeData => new TheoryData + { + new KestrelServerOptions(), + new KestrelServerOptions + { + Limits = { MaxResponseBufferSize = 0 } + }, + new KestrelServerOptions + { + Limits = { MaxResponseBufferSize = 1024 } + }, + new KestrelServerOptions + { + Limits = { MaxResponseBufferSize = 1024 * 1024 } + }, + new KestrelServerOptions + { + Limits = { MaxResponseBufferSize = null } + }, + }; + + public static TheoryData PositiveMaxResponseBufferSizeData => new TheoryData + { + new KestrelServerOptions(), + new KestrelServerOptions + { + Limits = { MaxResponseBufferSize = 1024 } + }, + new KestrelServerOptions + { + Limits = { MaxResponseBufferSize = 1024 * 1024 } + } + }; + + [Theory] + [MemberData(nameof(MaxResponseBufferSizeData))] + public async Task CanWrite1MB(KestrelServerOptions options) { // This test was added because when initially implementing write-behind buffering in // SocketOutput, the write callback would never be invoked for writes larger than @@ -33,10 +69,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); var ltp = new SynchronousThreadPool(); - var socketOutput = new SocketOutput(kestrelThread, socket, new MockConnection(), "0", trace, ltp); + var socketOutput = new SocketOutput(kestrelThread, socket, new MockConnection(options), "0", trace, ltp); - // I doubt _maxBytesPreCompleted will ever be over a MB. If it is, we should change this test. - var bufferSize = 1048576; + // At least one run of this test should have a MaxResponseBufferSize < 1 MB. + var bufferSize = 1024 * 1024; var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); // Act @@ -53,10 +89,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } [Fact] - public async Task WritesDontCompleteImmediatelyWhenTooManyBytesAreAlreadyBuffered() + public async Task NullMaxResponseBufferSizeAllowsUnlimitedBuffer() { - // This should match _maxBytesPreCompleted in SocketOutput - var maxBytesPreCompleted = 65536; var completeQueue = new ConcurrentQueue>(); // Arrange @@ -78,7 +112,121 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); var ltp = new SynchronousThreadPool(); - var mockConnection = new MockConnection(); + var options = new KestrelServerOptions { Limits = { MaxResponseBufferSize = null } }; + var socketOutput = new SocketOutput(kestrelThread, socket, new MockConnection(options), "0", trace, ltp); + + // Don't want to allocate anything too huge for perf. This is at least larger than the default buffer. + var bufferSize = 1024 * 1024; + var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); + + // Act + var writeTask = socketOutput.WriteAsync(buffer, default(CancellationToken)); + + // Assert + Assert.Equal(TaskStatus.RanToCompletion, writeTask.Status); + + // Cleanup + var cleanupTask = socketOutput.WriteAsync( + default(ArraySegment), default(CancellationToken), socketDisconnect: true); + + // Wait for all writes to complete so the completeQueue isn't modified during enumeration. + await mockLibuv.OnPostTask; + + foreach (var triggerCompleted in completeQueue) + { + triggerCompleted(0); + } + } + } + + [Fact] + public async Task ZeroMaxResponseBufferSizeDisablesBuffering() + { + var completeQueue = new ConcurrentQueue>(); + + // Arrange + var mockLibuv = new MockLibuv + { + OnWrite = (socket, buffers, triggerCompleted) => + { + completeQueue.Enqueue(triggerCompleted); + return 0; + } + }; + + using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext())) + { + var kestrelThread = new KestrelThread(kestrelEngine, maxLoops: 1); + kestrelEngine.Threads.Add(kestrelThread); + await kestrelThread.StartAsync(); + + var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new SynchronousThreadPool(); + var options = new KestrelServerOptions { Limits = { MaxResponseBufferSize = 0 } }; + var socketOutput = new SocketOutput(kestrelThread, socket, new MockConnection(options), "0", trace, ltp); + + var bufferSize = 1; + var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); + + // Act + var writeTask = socketOutput.WriteAsync(buffer, default(CancellationToken)); + + // Assert + Assert.False(writeTask.IsCompleted); + + // Act + await mockLibuv.OnPostTask; + + // Finishing the write should allow the task to complete. + Action triggerNextCompleted; + Assert.True(completeQueue.TryDequeue(out triggerNextCompleted)); + triggerNextCompleted(0); + + // Assert + Assert.Equal(TaskStatus.RanToCompletion, writeTask.Status); + + // Cleanup + var cleanupTask = socketOutput.WriteAsync( + default(ArraySegment), default(CancellationToken), socketDisconnect: true); + + // Wait for all writes to complete so the completeQueue isn't modified during enumeration. + await mockLibuv.OnPostTask; + + foreach (var triggerCompleted in completeQueue) + { + triggerCompleted(0); + } + } + } + + [Theory] + [MemberData(nameof(PositiveMaxResponseBufferSizeData))] + public async Task WritesDontCompleteImmediatelyWhenTooManyBytesAreAlreadyBuffered(KestrelServerOptions options) + { + var maxBytesPreCompleted = (int)options.Limits.MaxResponseBufferSize.Value; + var completeQueue = new ConcurrentQueue>(); + + // Arrange + var mockLibuv = new MockLibuv + { + OnWrite = (socket, buffers, triggerCompleted) => + { + completeQueue.Enqueue(triggerCompleted); + return 0; + } + }; + + using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext())) + { + var kestrelThread = new KestrelThread(kestrelEngine, maxLoops: 1); + kestrelEngine.Threads.Add(kestrelThread); + await kestrelThread.StartAsync(); + + var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new SynchronousThreadPool(); + var mockConnection = new MockConnection(options); var socketOutput = new SocketOutput(kestrelThread, socket, mockConnection, "0", trace, ltp); var bufferSize = maxBytesPreCompleted; @@ -122,11 +270,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } } - [Fact] - public async Task WritesDontCompleteImmediatelyWhenTooManyBytesIncludingNonImmediateAreAlreadyBuffered() + [Theory] + [MemberData(nameof(PositiveMaxResponseBufferSizeData))] + public async Task WritesDontCompleteImmediatelyWhenTooManyBytesIncludingNonImmediateAreAlreadyBuffered(KestrelServerOptions options) { - // This should match _maxBytesPreCompleted in SocketOutput - var maxBytesPreCompleted = 65536; + var maxBytesPreCompleted = (int)options.Limits.MaxResponseBufferSize.Value; var completeQueue = new ConcurrentQueue>(); var writeRequested = false; @@ -150,7 +298,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); var ltp = new SynchronousThreadPool(); - var mockConnection = new MockConnection(); + var mockConnection = new MockConnection(options); var socketOutput = new SocketOutput(kestrelThread, socket, mockConnection, "0", trace, ltp); var bufferSize = maxBytesPreCompleted / 2; @@ -207,11 +355,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } } - [Fact] - public async Task OnlyWritesRequestingCancellationAreErroredOnCancellation() + [Theory] + [MemberData(nameof(PositiveMaxResponseBufferSizeData))] + public async Task OnlyWritesRequestingCancellationAreErroredOnCancellation(KestrelServerOptions options) { - // This should match _maxBytesPreCompleted in SocketOutput - var maxBytesPreCompleted = 65536; + var maxBytesPreCompleted = (int)options.Limits.MaxResponseBufferSize.Value; var completeQueue = new ConcurrentQueue>(); // Arrange @@ -234,7 +382,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var trace = new KestrelTrace(new TestKestrelTrace()); var ltp = new SynchronousThreadPool(); - using (var mockConnection = new MockConnection()) + using (var mockConnection = new MockConnection(options)) { ISocketOutput socketOutput = new SocketOutput(kestrelThread, socket, mockConnection, "0", trace, ltp); @@ -324,11 +472,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } } - [Fact] - public async Task FailedWriteCompletesOrCancelsAllPendingTasks() + [Theory] + [MemberData(nameof(PositiveMaxResponseBufferSizeData))] + public async Task FailedWriteCompletesOrCancelsAllPendingTasks(KestrelServerOptions options) { - // This should match _maxBytesPreCompleted in SocketOutput - var maxBytesPreCompleted = 65536; + var maxBytesPreCompleted = (int)options.Limits.MaxResponseBufferSize.Value; var completeQueue = new ConcurrentQueue>(); // Arrange @@ -351,7 +499,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var trace = new KestrelTrace(new TestKestrelTrace()); var ltp = new SynchronousThreadPool(); - using (var mockConnection = new MockConnection()) + using (var mockConnection = new MockConnection(options)) { var abortedSource = mockConnection.RequestAbortedSource; ISocketOutput socketOutput = new SocketOutput(kestrelThread, socket, mockConnection, "0", trace, ltp); @@ -414,11 +562,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } } - [Fact] - public async Task WritesDontGetCompletedTooQuickly() + [Theory] + [MemberData(nameof(PositiveMaxResponseBufferSizeData))] + public async Task WritesDontGetCompletedTooQuickly(KestrelServerOptions options) { - // This should match _maxBytesPreCompleted in SocketOutput - var maxBytesPreCompleted = 65536; + var maxBytesPreCompleted = (int)options.Limits.MaxResponseBufferSize.Value; var completeQueue = new ConcurrentQueue>(); var writeCalled = false; @@ -443,7 +591,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); var ltp = new SynchronousThreadPool(); - var mockConnection = new MockConnection(); + var mockConnection = new MockConnection(options); var socketOutput = new SocketOutput(kestrelThread, socket, mockConnection, "0", trace, ltp); var bufferSize = maxBytesPreCompleted; @@ -498,8 +646,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } } - [Fact] - public async Task ProducingStartAndProducingCompleteCanBeUsedDirectly() + [Theory] + [MemberData(nameof(MaxResponseBufferSizeData))] + public async Task ProducingStartAndProducingCompleteCanBeUsedDirectly(KestrelServerOptions options) { int nBuffers = 0; @@ -522,7 +671,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); var ltp = new SynchronousThreadPool(); - var socketOutput = new SocketOutput(kestrelThread, socket, new MockConnection(), "0", trace, ltp); + var socketOutput = new SocketOutput(kestrelThread, socket, new MockConnection(options), "0", trace, ltp); // block 1 var start = socketOutput.ProducingStart(); @@ -549,8 +698,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } } - [Fact] - public async Task OnlyAllowsUpToThreeConcurrentWrites() + [Theory] + [MemberData(nameof(MaxResponseBufferSizeData))] + public async Task OnlyAllowsUpToThreeConcurrentWrites(KestrelServerOptions options) { var writeCalled = false; var completeQueue = new ConcurrentQueue>(); @@ -574,7 +724,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); var ltp = new SynchronousThreadPool(); - var mockConnection = new MockConnection(); + var mockConnection = new MockConnection(options); var socketOutput = new SocketOutput(kestrelThread, socket, mockConnection, "0", trace, ltp); var buffer = new ArraySegment(new byte[1]); @@ -619,8 +769,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } } - [Fact] - public async Task WritesAreAggregated() + [Theory] + [MemberData(nameof(MaxResponseBufferSizeData))] + public async Task WritesAreAggregated(KestrelServerOptions options) { var writeCalled = false; var writeCount = 0; @@ -645,7 +796,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); var ltp = new SynchronousThreadPool(); - var socketOutput = new SocketOutput(kestrelThread, socket, new MockConnection(), "0", trace, ltp); + var socketOutput = new SocketOutput(kestrelThread, socket, new MockConnection(new KestrelServerOptions()), "0", trace, ltp); mockLibuv.KestrelThreadBlocker.Reset(); @@ -691,7 +842,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); var ltp = new SynchronousThreadPool(); - var connection = new MockConnection(); + var connection = new MockConnection(new KestrelServerOptions()); var socketOutput = new SocketOutput(kestrelThread, socket, connection, "0", trace, ltp); // Close SocketOutput diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestHelpers/MockConnection.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestHelpers/MockConnection.cs index 619d4d5b7b..930c1ec6d8 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/TestHelpers/MockConnection.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/TestHelpers/MockConnection.cs @@ -4,25 +4,24 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers { public class MockConnection : Connection, IDisposable { - private TaskCompletionSource _socketClosedTcs = new TaskCompletionSource(); + private readonly TaskCompletionSource _socketClosedTcs = new TaskCompletionSource(); - public MockConnection() + public MockConnection(KestrelServerOptions options) { RequestAbortedSource = new CancellationTokenSource(); + ServerOptions = options; } public override void Abort(Exception error = null) { - if (RequestAbortedSource != null) - { - RequestAbortedSource.Cancel(); - } + RequestAbortedSource?.Cancel(); } public override void OnSocketClosed() @@ -32,13 +31,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers public CancellationTokenSource RequestAbortedSource { get; } - public Task SocketClosed - { - get - { - return _socketClosedTcs.Task; - } - } + public Task SocketClosed => _socketClosedTcs.Task; public void Dispose() {