Gracefully handle connection close in SocketOutput.ProducingComplete
This commit is contained in:
parent
2d229e8980
commit
07744e75d9
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue