Gracefully handle connection close in SocketOutput.ProducingComplete

This commit is contained in:
Stephen Halter 2016-06-01 10:47:34 -07:00
parent 2d229e8980
commit 07744e75d9
4 changed files with 54 additions and 25 deletions

View File

@ -248,7 +248,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
public void ProducingComplete(MemoryPoolIterator end) public void ProducingComplete(MemoryPoolIterator end)
{ {
Debug.Assert(!_lastStart.IsDefault); if(_lastStart.IsDefault)
{
return;
}
int bytesProduced, buffersIncluded; int bytesProduced, buffersIncluded;
BytesBetween(_lastStart, end, out bytesProduced, out buffersIncluded); BytesBetween(_lastStart, end, out bytesProduced, out buffersIncluded);
@ -265,9 +268,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{ {
MemoryPoolBlock blockToReturn = null; MemoryPoolBlock blockToReturn = null;
lock (_returnLock) lock (_returnLock)
{ {
// Both ProducingComplete and WriteAsync should not call this method
// if _lastStart was not set.
Debug.Assert(!_lastStart.IsDefault); Debug.Assert(!_lastStart.IsDefault);
// If the socket has been closed, return the produced blocks // If the socket has been closed, return the produced blocks

View File

@ -24,14 +24,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
// _maxBytesPreCompleted even after the write actually completed. // _maxBytesPreCompleted even after the write actually completed.
// Arrange // Arrange
var mockLibuv = new MockLibuv var mockLibuv = new MockLibuv();
{
OnWrite = (socket, buffers, triggerCompleted) =>
{
triggerCompleted(0);
return 0;
}
};
using (var memory = new MemoryPool()) using (var memory = new MemoryPool())
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext())) using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
@ -661,5 +654,37 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
default(ArraySegment<byte>), default(CancellationToken), socketDisconnect: true); default(ArraySegment<byte>), default(CancellationToken), socketDisconnect: true);
} }
} }
[Fact]
public void ProducingStartAndProducingCompleteCanBeCalledAfterConnectionClose()
{
var mockLibuv = new MockLibuv();
using (var memory = new MemoryPool())
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
{
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
var connection = new MockConnection();
var socketOutput = new SocketOutput(kestrelThread, socket, memory, connection, "0", trace, ltp, new Queue<UvWriteReq>());
// Close SocketOutput
var cleanupTask = socketOutput.WriteAsync(
default(ArraySegment<byte>), default(CancellationToken), socketDisconnect: true);
Assert.True(connection.SocketClosed.Wait(1000));
var start = socketOutput.ProducingStart();
Assert.True(start.IsDefault);
// ProducingComplete should not throw given a default iterator
socketOutput.ProducingComplete(start);
}
}
} }
} }

View File

@ -3,12 +3,15 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Http; using Microsoft.AspNetCore.Server.Kestrel.Http;
namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
{ {
public class MockConnection : Connection, IDisposable public class MockConnection : Connection, IDisposable
{ {
private TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>();
public MockConnection() public MockConnection()
{ {
RequestAbortedSource = new CancellationTokenSource(); RequestAbortedSource = new CancellationTokenSource();
@ -24,10 +27,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
public override void OnSocketClosed() public override void OnSocketClosed()
{ {
_socketClosedTcs.SetResult(null);
} }
public CancellationTokenSource RequestAbortedSource { get; } public CancellationTokenSource RequestAbortedSource { get; }
public Task SocketClosed
{
get
{
return _socketClosedTcs.Task;
}
}
public void Dispose() public void Dispose()
{ {
RequestAbortedSource.Dispose(); RequestAbortedSource.Dispose();

View File

@ -12,12 +12,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
private bool _stopLoop; private bool _stopLoop;
private readonly ManualResetEventSlim _loopWh = new ManualResetEventSlim(); private readonly ManualResetEventSlim _loopWh = new ManualResetEventSlim();
private Func<UvStreamHandle, int, Action<int>, int> _onWrite;
unsafe public MockLibuv() unsafe public MockLibuv()
: base(onlyForTesting: true) : base(onlyForTesting: true)
{ {
_onWrite = (socket, buffers, triggerCompleted) => OnWrite = (socket, buffers, triggerCompleted) =>
{ {
triggerCompleted(0); triggerCompleted(0);
return 0; return 0;
@ -80,17 +78,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
_uv_read_stop = handle => 0; _uv_read_stop = handle => 0;
} }
public Func<UvStreamHandle, int, Action<int>, int> OnWrite public Func<UvStreamHandle, int, Action<int>, int> OnWrite { get; set; }
{
get
{
return _onWrite;
}
set
{
_onWrite = value;
}
}
public uv_alloc_cb AllocCallback { get; set; } public uv_alloc_cb AllocCallback { get; set; }
@ -107,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
unsafe private int UvWrite(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, uv_write_cb cb) unsafe private int UvWrite(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, uv_write_cb cb)
{ {
return _onWrite(handle, nbufs, status => cb(req.InternalGetHandle(), status)); return OnWrite(handle, nbufs, status => cb(req.InternalGetHandle(), status));
} }
} }
} }