Handle 0-byte reads correctly (#520).

This commit is contained in:
Cesar Blum Silveira 2016-02-18 15:48:13 -08:00
parent 4bcc44faed
commit f21cb128e8
6 changed files with 122 additions and 28 deletions

View File

@ -16,9 +16,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
// Base32 encoding - in ascii sort order for easy text based sorting
private static readonly string _encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
private static readonly Action<UvStreamHandle, int, object> _readCallback =
private static readonly Action<UvStreamHandle, int, object> _readCallback =
(handle, status, state) => ReadCallback(handle, status, state);
private static readonly Func<UvStreamHandle, int, object, Libuv.uv_buf_t> _allocCallback =
private static readonly Func<UvStreamHandle, int, object, Libuv.uv_buf_t> _allocCallback =
(handle, suggestedsize, state) => AllocCallback(handle, suggestedsize, state);
// Seed the _lastConnectionId for this application instance with
@ -252,8 +252,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private void OnRead(UvStreamHandle handle, int status)
{
if (status == 0)
{
// A zero status does not indicate an error or connection end. It indicates
// there is no data to be read right now.
// See the note at http://docs.libuv.org/en/v1.x/stream.html#c.uv_read_cb.
// We need to clean up whatever was allocated by OnAlloc.
_rawSocketInput.IncomingDeferred();
return;
}
var normalRead = status > 0;
var normalDone = status == 0 || status == Constants.ECONNRESET || status == Constants.EOF;
var normalDone = status == Constants.ECONNRESET || status == Constants.EOF;
var errorDone = !(normalDone || normalRead);
var readCount = normalRead ? status : 0;

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
@ -117,6 +118,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
Complete();
}
public void IncomingDeferred()
{
Debug.Assert(_pinned != null);
if (_pinned != null)
{
if (_pinned != _tail)
{
_memory.Return(_pinned);
}
_pinned = null;
}
}
private void Complete()
{
var awaitableState = Interlocked.Exchange(

View File

@ -0,0 +1,46 @@
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Http;
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Networking;
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
using Xunit;
namespace Microsoft.AspNetCore.Server.KestrelTests
{
public class ConnectionTests
{
[Fact]
public void DoesNotEndConnectionOnZeroRead()
{
var mockLibuv = new MockLibuv();
using (var memory = new MemoryPool2())
using (var engine = new KestrelEngine(mockLibuv, new TestServiceContext()))
{
engine.Start(count: 1);
var trace = new TestKestrelTrace();
var context = new ListenerContext(new TestServiceContext())
{
FrameFactory = connectionContext => new Frame<HttpContext>(
new DummyApplication(httpContext => TaskUtilities.CompletedTask), connectionContext),
Memory2 = memory,
ServerAddress = ServerAddress.FromUrl($"http://localhost:{TestServer.GetNextPort()}"),
Thread = engine.Threads[0]
};
var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, trace);
var connection = new Connection(context, socket);
connection.Start();
Libuv.uv_buf_t ignored;
mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out ignored);
mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored);
Assert.False(connection.SocketInput.RemoteIntakeFin);
connection.ConnectionControl.End(ProduceEndType.SocketDisconnect);
}
}
}
}

View File

@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
var socketOutput = new SocketOutput(kestrelThread, socket, memory, new MockConnection(), "0", trace, ltp, new Queue<UvWriteReq>());
@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
var socketOutput = new SocketOutput(kestrelThread, socket, memory, new MockConnection(), "0", trace, ltp, new Queue<UvWriteReq>());
@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
var socketOutput = new SocketOutput(kestrelThread, socket, memory, new MockConnection(), "0", trace, ltp, new Queue<UvWriteReq>());
@ -231,7 +231,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -341,7 +341,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -429,7 +429,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
var socketOutput = new SocketOutput(kestrelThread, socket, memory, new MockConnection(), "0", trace, ltp, new Queue<UvWriteReq>());
@ -515,7 +515,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
var socketOutput = new SocketOutput(kestrelThread, socket, memory, new MockConnection(), "0", trace, ltp, new Queue<UvWriteReq>());
@ -544,23 +544,5 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
default(ArraySegment<byte>), default(CancellationToken), socketDisconnect: true);
}
}
private class MockSocket : UvStreamHandle
{
public MockSocket(int threadId, IKestrelTrace logger) : base(logger)
{
// Set the handle to something other than IntPtr.Zero
// so handle.Validate doesn't fail in Libuv.write
handle = (IntPtr)1;
_threadId = threadId;
}
protected override bool ReleaseHandle()
{
// No-op
return true;
}
}
}
}

View File

@ -17,6 +17,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
unsafe public MockLibuv()
: base(onlyForTesting: true)
{
_onWrite = (socket, buffers, triggerCompleted) =>
{
triggerCompleted(0);
return 0;
};
_uv_write = UvWrite;
_uv_async_send = postHandle =>
@ -69,6 +75,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
_uv_walk = (loop, callback, ignore) => 0;
_uv_err_name = errno => IntPtr.Zero;
_uv_strerror = errno => IntPtr.Zero;
_uv_read_start = UvReadStart;
_uv_read_stop = handle => 0;
}
public Func<UvStreamHandle, int, Action<int>, int> OnWrite
@ -83,6 +91,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
}
}
public uv_alloc_cb AllocCallback { get; set; }
public uv_read_cb ReadCallback { get; set; }
private int UvReadStart(UvStreamHandle handle, uv_alloc_cb allocCallback, uv_read_cb readCallback)
{
AllocCallback = allocCallback;
ReadCallback = readCallback;
return 0;
}
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));

View File

@ -0,0 +1,21 @@
using System;
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Networking;
namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
{
class MockSocket : UvStreamHandle
{
public MockSocket(Libuv uv, int threadId, IKestrelTrace logger) : base(logger)
{
CreateMemory(uv, threadId, IntPtr.Size);
}
protected override bool ReleaseHandle()
{
DestroyMemory(handle);
handle = IntPtr.Zero;
return true;
}
}
}