Fix NRE when aborting connection or client disconects

This commit is contained in:
Pavel Krymets 2016-05-31 09:24:46 -07:00
parent 6224f5b6e8
commit bb92cc1c29
3 changed files with 73 additions and 38 deletions

View File

@ -27,9 +27,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private MemoryPoolBlock _tail; private MemoryPoolBlock _tail;
private MemoryPoolBlock _pinned; private MemoryPoolBlock _pinned;
private int _consumingState;
private object _sync = new object(); private object _sync = new object();
private bool _consuming;
private bool _disposed;
public SocketInput(MemoryPool memory, IThreadPool threadPool) public SocketInput(MemoryPool memory, IThreadPool threadPool)
{ {
_memory = memory; _memory = memory;
@ -163,13 +165,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
public MemoryPoolIterator ConsumingStart() public MemoryPoolIterator ConsumingStart()
{ {
if (Interlocked.CompareExchange(ref _consumingState, 1, 0) != 0) lock (_sync)
{
if (_consuming)
{ {
throw new InvalidOperationException("Already consuming input."); throw new InvalidOperationException("Already consuming input.");
} }
_consuming = true;
return new MemoryPoolIterator(_head); return new MemoryPoolIterator(_head);
} }
}
public void ConsumingComplete( public void ConsumingComplete(
MemoryPoolIterator consumed, MemoryPoolIterator consumed,
@ -179,6 +184,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
MemoryPoolBlock returnEnd = null; MemoryPoolBlock returnEnd = null;
lock (_sync) lock (_sync)
{
if (!_disposed)
{ {
if (!consumed.IsDefault) if (!consumed.IsDefault)
{ {
@ -201,18 +208,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_awaitableIsCompleted); _awaitableIsCompleted);
} }
} }
else
while (returnStart != returnEnd)
{ {
var returnBlock = returnStart; returnStart = _head;
returnStart = returnStart.Next; returnEnd = null;
returnBlock.Pool.Return(returnBlock); _head = null;
_tail = null;
} }
if (Interlocked.CompareExchange(ref _consumingState, 0, 1) != 1) ReturnBlocks(returnStart, returnEnd);
if (!_consuming)
{ {
throw new InvalidOperationException("No ongoing consuming operation to complete."); throw new InvalidOperationException("No ongoing consuming operation to complete.");
} }
_consuming = false;
}
} }
public void CompleteAwaiting() public void CompleteAwaiting()
@ -285,21 +296,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
} }
public void Dispose() public void Dispose()
{
lock (_sync)
{ {
AbortAwaiting(); AbortAwaiting();
// Return all blocks if (!_consuming)
var block = _head; {
while (block != null) ReturnBlocks(_head, null);
_head = null;
_tail = null;
}
_disposed = true;
}
}
private static void ReturnBlocks(MemoryPoolBlock block, MemoryPoolBlock end)
{
while (block != end)
{ {
var returnBlock = block; var returnBlock = block;
block = block.Next; block = block.Next;
returnBlock.Pool.Return(returnBlock); returnBlock.Pool.Return(returnBlock);
} }
_head = null;
_tail = null;
} }
} }
} }

View File

@ -29,6 +29,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
public static Task<int> GetCancelledZeroTask(CancellationToken cancellationToken = default(CancellationToken)) public static Task<int> GetCancelledZeroTask(CancellationToken cancellationToken = default(CancellationToken))
{ {
#if NETSTANDARD1_3 #if NETSTANDARD1_3
// Make sure cancellationToken is cancelled before passing to Task.FromCanceled
if (!cancellationToken.IsCancellationRequested)
{
cancellationToken = new CancellationToken(true);
}
return Task.FromCanceled<int>(cancellationToken); return Task.FromCanceled<int>(cancellationToken);
#else #else
var tcs = new TaskCompletionSource<int>(); var tcs = new TaskCompletionSource<int>();

View File

@ -104,5 +104,15 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var stream = new FrameRequestStream(); var stream = new FrameRequestStream();
await stream.FlushAsync(); await stream.FlushAsync();
} }
[Fact]
public void AbortCausesReadToCancel()
{
var stream = new FrameRequestStream();
stream.StartAcceptingReads(null);
stream.Abort();
var task = stream.ReadAsync(new byte[1], 0, 1);
Assert.True(task.IsCanceled);
}
} }
} }