* Properly handle FINs and resets in the SocketConnection (#1782)

- FIN from the client shouldn't throw
- Forced close from the server should throw
- Properly wrap connection reset exceptions and other exceptions
in IO exceptions
- This gives kestrel control over when the output closes
- Fixed one test that assumed libuv
- Dispose the connection to yield the reader

Fixes #1774
This commit is contained in:
David Fowler 2017-04-27 17:55:35 -07:00 committed by GitHub
parent 4799c0e423
commit 5b62024fc8
2 changed files with 48 additions and 16 deletions

View File

@ -4,8 +4,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.Buffers; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.Buffers;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
@ -23,8 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
private IPipeWriter _input; private IPipeWriter _input;
private IPipeReader _output; private IPipeReader _output;
private IList<ArraySegment<byte>> _sendBufferList; private IList<ArraySegment<byte>> _sendBufferList;
private const int MinAllocBufferSize = 2048;
private const int MinAllocBufferSize = 2048; // from libuv transport
internal SocketConnection(Socket socket, SocketTransport transport) internal SocketConnection(Socket socket, SocketTransport transport)
{ {
@ -51,17 +52,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
Task receiveTask = DoReceive(); Task receiveTask = DoReceive();
Task sendTask = DoSend(); Task sendTask = DoSend();
// Wait for eiher of them to complete (note they won't throw exceptions) // If the sending task completes then close the receive
await Task.WhenAny(receiveTask, sendTask); // We don't need to do this in the other direction because the kestrel
// will trigger the output closing once the input is complete.
// Shut the socket down and wait for both sides to end if (await Task.WhenAny(receiveTask, sendTask) == sendTask)
_socket.Shutdown(SocketShutdown.Both); {
// Tell the reader it's being aborted
_socket.Dispose();
}
// Now wait for both to complete // Now wait for both to complete
await receiveTask; await receiveTask;
await sendTask; await sendTask;
// Dispose the socket // Dispose the socket(should noop if already called)
_socket.Dispose(); _socket.Dispose();
} }
catch (Exception) catch (Exception)
@ -90,9 +94,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
if (bytesReceived == 0) if (bytesReceived == 0)
{ {
// We receive a FIN so throw an exception so that we cancel the input // FIN
// with an error break;
throw new TaskCanceledException("The request was aborted");
} }
buffer.Advance(bytesReceived); buffer.Advance(bytesReceived);
@ -106,17 +109,45 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
if (result.IsCompleted) if (result.IsCompleted)
{ {
// Pipe consumer is shut down, do we stop writing // Pipe consumer is shut down, do we stop writing
_socket.Shutdown(SocketShutdown.Receive);
break; break;
} }
} }
_connectionContext.Abort(ex: null);
_input.Complete(); _input.Complete();
} }
catch (Exception ex) catch (Exception ex)
{ {
_connectionContext.Abort(ex); Exception error = null;
_input.Complete(ex);
if (ex is SocketException se)
{
if (se.SocketErrorCode == SocketError.ConnectionReset)
{
// Connection reset
error = new ConnectionResetException(ex.Message, ex);
}
else if (se.SocketErrorCode == SocketError.OperationAborted)
{
error = new TaskCanceledException("The request was aborted");
}
}
if (ex is ObjectDisposedException)
{
error = new TaskCanceledException("The request was aborted");
}
else if (ex is IOException ioe)
{
error = ioe;
}
else if (error == null)
{
error = new IOException(ex.Message, ex);
}
_connectionContext.Abort(error);
_input.Complete(error);
} }
} }

View File

@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
@ -462,9 +463,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{ {
await context.Request.Body.ReadAsync(new byte[1], 0, 1); await context.Request.Body.ReadAsync(new byte[1], 0, 1);
} }
catch (IOException ex) catch (ConnectionResetException)
{ {
expectedExceptionThrown = ex.InnerException is UvException && ex.InnerException.Message.Contains("ECONNRESET"); expectedExceptionThrown = true;
} }
appDone.Release(); appDone.Release();