Add initial Socket transport for Kestrel (#1659)
- This change adds the initial socket transport for Kestrel, all of the tests pass but there are still a couple of things that aren't done yet. - The functional tests support running both on both transports but tests aren't running for sockets right now. We need to parameterize these. - TimeoutServerTests hard code the libuv transport, this needs to support any transport. - There is no handling of connection stopping on application shutdown. This is being implemented in kestrel core so transports don't need to handle it. Sockets won't be the default transport until that is the case. - Performance needs to be looked at, today the SocketTransport doesn't dispatch by default and we're not buffering in kestrel.core, this can hurt as the number of kernel calls map 1:1 with application writes.
This commit is contained in:
parent
0723d46ec4
commit
efa0a48fb1
|
|
@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel", "src\Microsoft.AspNetCore.Server.Kestrel\Microsoft.AspNetCore.Server.Kestrel.csproj", "{56139957-5C29-4E7D-89BD-7D20598B4EAF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets", "src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", "{6950B18F-A3D2-41A4-AFEC-8B7C49517611}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions", "src\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj", "{2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests", "test\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.csproj", "{D95A7EC3-48AC-4D03-B2E2-0DA3E13BD3A4}"
|
||||
|
|
@ -198,6 +200,18 @@ Global
|
|||
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x64.Build.0 = Release|Any CPU
|
||||
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|x86.Build.0 = Release|Any CPU
|
||||
{2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -251,6 +265,7 @@ Global
|
|||
{2822C132-BFFB-4D53-AC5B-E7E47DD81A6E} = {0EF2ACDF-012F-4472-A13A-4272419E2903}
|
||||
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
|
||||
{56139957-5C29-4E7D-89BD-7D20598B4EAF} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
|
||||
{6950B18F-A3D2-41A4-AFEC-8B7C49517611} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
|
||||
{2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
|
||||
{D95A7EC3-48AC-4D03-B2E2-0DA3E13BD3A4} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
|
||||
{4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
|
||||
public IConnectionContext OnConnection(IConnectionInformation connectionInfo)
|
||||
{
|
||||
var inputPipe = connectionInfo.PipeFactory.Create(GetInputPipeOptions(connectionInfo.InputWriterScheduler));
|
||||
var outputPipe = connectionInfo.PipeFactory.Create(GetOutputPipeOptions(connectionInfo.OutputReaderScheduler));
|
||||
var inputPipe = connectionInfo.PipeFactory.Create(GetInputPipeOptions(requiresDispatch: connectionInfo.RequiresDispatch, writerScheduler: connectionInfo.InputWriterScheduler));
|
||||
var outputPipe = connectionInfo.PipeFactory.Create(GetOutputPipeOptions(requiresDispatch: connectionInfo.RequiresDispatch, readerScheduler: connectionInfo.OutputReaderScheduler));
|
||||
|
||||
var connectionId = CorrelationIdGenerator.GetNextId();
|
||||
var frameConnectionId = Interlocked.Increment(ref _lastFrameConnectionId);
|
||||
|
|
@ -70,18 +70,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
}
|
||||
|
||||
// Internal for testing
|
||||
internal PipeOptions GetInputPipeOptions(IScheduler writerScheduler) => new PipeOptions
|
||||
internal PipeOptions GetInputPipeOptions(bool requiresDispatch, IScheduler writerScheduler) => new PipeOptions
|
||||
{
|
||||
ReaderScheduler = _serviceContext.ThreadPool,
|
||||
ReaderScheduler = (requiresDispatch ? (IScheduler)_serviceContext.ThreadPool : (IScheduler)InlineScheduler.Default),
|
||||
WriterScheduler = writerScheduler,
|
||||
MaximumSizeHigh = _serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0,
|
||||
MaximumSizeLow = _serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0
|
||||
};
|
||||
|
||||
internal PipeOptions GetOutputPipeOptions(IScheduler readerScheduler) => new PipeOptions
|
||||
internal PipeOptions GetOutputPipeOptions(bool requiresDispatch, IScheduler readerScheduler) => new PipeOptions
|
||||
{
|
||||
ReaderScheduler = readerScheduler,
|
||||
WriterScheduler = _serviceContext.ThreadPool,
|
||||
WriterScheduler = (requiresDispatch ? (IScheduler)_serviceContext.ThreadPool : (IScheduler)InlineScheduler.Default),
|
||||
MaximumSizeHigh = GetOutputResponseBufferSize(),
|
||||
MaximumSizeLow = GetOutputResponseBufferSize()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions
|
|||
IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
PipeFactory PipeFactory { get; }
|
||||
|
||||
bool RequiresDispatch { get; }
|
||||
|
||||
IScheduler InputWriterScheduler { get; }
|
||||
IScheduler OutputReaderScheduler { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
public IPEndPoint LocalEndPoint { get; set; }
|
||||
|
||||
public PipeFactory PipeFactory => ListenerContext.Thread.PipeFactory;
|
||||
public bool RequiresDispatch => true;
|
||||
public IScheduler InputWriterScheduler => ListenerContext.Thread;
|
||||
public IScheduler OutputReaderScheduler => ListenerContext.Thread;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Managed socket transport for the ASP.NET Core Kestrel cross-platform web server.</Description>
|
||||
<TargetFrameworks>netstandard1.3</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore;kestrel</PackageTags>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<NoWarn>CS1591;$(NoWarn)</NoWarn>
|
||||
<EnableApiCheck>false</EnableApiCheck>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(AspNetCoreVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="$(AspNetCoreVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.Buffers;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
|
||||
{
|
||||
internal sealed class SocketConnection : IConnectionInformation
|
||||
{
|
||||
private readonly Socket _socket;
|
||||
private readonly SocketTransport _transport;
|
||||
private readonly IPEndPoint _localEndPoint;
|
||||
private readonly IPEndPoint _remoteEndPoint;
|
||||
private IConnectionContext _connectionContext;
|
||||
private IPipeWriter _input;
|
||||
private IPipeReader _output;
|
||||
private IList<ArraySegment<byte>> _sendBufferList;
|
||||
|
||||
private const int MinAllocBufferSize = 2048; // from libuv transport
|
||||
|
||||
internal SocketConnection(Socket socket, SocketTransport transport)
|
||||
{
|
||||
Debug.Assert(socket != null);
|
||||
Debug.Assert(transport != null);
|
||||
|
||||
_socket = socket;
|
||||
_transport = transport;
|
||||
|
||||
_localEndPoint = (IPEndPoint)_socket.LocalEndPoint;
|
||||
_remoteEndPoint = (IPEndPoint)_socket.RemoteEndPoint;
|
||||
}
|
||||
|
||||
public async void Start(IConnectionHandler connectionHandler)
|
||||
{
|
||||
_connectionContext = connectionHandler.OnConnection(this);
|
||||
|
||||
_input = _connectionContext.Input;
|
||||
_output = _connectionContext.Output;
|
||||
|
||||
// Spawn send and receive logic
|
||||
Task receiveTask = DoReceive();
|
||||
Task sendTask = DoSend();
|
||||
|
||||
// Wait for them to complete (note they won't throw exceptions)
|
||||
await receiveTask;
|
||||
await sendTask;
|
||||
|
||||
_socket.Dispose();
|
||||
|
||||
_connectionContext.OnConnectionClosed();
|
||||
}
|
||||
|
||||
private async Task DoReceive()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool done = false;
|
||||
while (!done)
|
||||
{
|
||||
// Ensure we have some reasonable amount of buffer space
|
||||
WritableBuffer buffer = _input.Alloc(MinAllocBufferSize);
|
||||
|
||||
int bytesReceived;
|
||||
try
|
||||
{
|
||||
bytesReceived = await _socket.ReceiveAsync(GetArraySegment(buffer.Buffer), SocketFlags.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
buffer.Commit();
|
||||
_connectionContext.Abort(ex);
|
||||
_input.Complete(ex);
|
||||
break;
|
||||
}
|
||||
|
||||
if (bytesReceived == 0)
|
||||
{
|
||||
// EOF
|
||||
Exception ex = new TaskCanceledException();
|
||||
buffer.Commit();
|
||||
_connectionContext.Abort(ex);
|
||||
_input.Complete(ex);
|
||||
break;
|
||||
}
|
||||
|
||||
// record what data we filled into the buffer and push to pipe
|
||||
buffer.Advance(bytesReceived);
|
||||
var result = await buffer.FlushAsync();
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
// Pipe consumer is shut down
|
||||
_socket.Shutdown(SocketShutdown.Receive);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// We don't expect any exceptions here, but eat it anyway as caller does not handle this.
|
||||
Debug.Assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupSendBuffers(ReadableBuffer buffer)
|
||||
{
|
||||
Debug.Assert(!buffer.IsEmpty);
|
||||
Debug.Assert(!buffer.IsSingleSpan);
|
||||
|
||||
if (_sendBufferList == null)
|
||||
{
|
||||
_sendBufferList = new List<ArraySegment<byte>>();
|
||||
}
|
||||
|
||||
// We should always clear the list after the send
|
||||
Debug.Assert(_sendBufferList.Count == 0);
|
||||
|
||||
foreach (var b in buffer)
|
||||
{
|
||||
_sendBufferList.Add(GetArraySegment(b));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoSend()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool done = false;
|
||||
while (!done)
|
||||
{
|
||||
// Wait for data to write from the pipe producer
|
||||
ReadResult result = await _output.ReadAsync();
|
||||
ReadableBuffer buffer = result.Buffer;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (buffer.IsSingleSpan)
|
||||
{
|
||||
await _socket.SendAsync(GetArraySegment(buffer.First), SocketFlags.None);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupSendBuffers(buffer);
|
||||
try
|
||||
{
|
||||
await _socket.SendAsync(_sendBufferList, SocketFlags.None);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sendBufferList.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.IsCancelled)
|
||||
{
|
||||
// Send a FIN
|
||||
_socket.Shutdown(SocketShutdown.Send);
|
||||
break;
|
||||
}
|
||||
|
||||
if (buffer.IsEmpty && result.IsCompleted)
|
||||
{
|
||||
// Send a FIN
|
||||
_socket.Shutdown(SocketShutdown.Send);
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_output.Advance(buffer.End);
|
||||
}
|
||||
}
|
||||
|
||||
// We're done reading
|
||||
_output.Complete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_output.Complete(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static ArraySegment<byte> GetArraySegment(Buffer<byte> buffer)
|
||||
{
|
||||
ArraySegment<byte> segment;
|
||||
if (!buffer.TryGetArray(out segment))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
public IPEndPoint RemoteEndPoint => _remoteEndPoint;
|
||||
|
||||
public IPEndPoint LocalEndPoint => _localEndPoint;
|
||||
|
||||
public PipeFactory PipeFactory => _transport.TransportFactory.PipeFactory;
|
||||
|
||||
public bool RequiresDispatch => _transport.TransportFactory.ForceDispatch;
|
||||
|
||||
public IScheduler InputWriterScheduler => InlineScheduler.Default;
|
||||
|
||||
public IScheduler OutputReaderScheduler => InlineScheduler.Default;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
|
||||
{
|
||||
internal sealed class SocketTransport : ITransport
|
||||
{
|
||||
private readonly SocketTransportFactory _transportFactory;
|
||||
private readonly IEndPointInformation _endPointInformation;
|
||||
private readonly IConnectionHandler _handler;
|
||||
private Socket _listenSocket;
|
||||
private Task _listenTask;
|
||||
|
||||
internal SocketTransport(SocketTransportFactory transportFactory, IEndPointInformation endPointInformation, IConnectionHandler handler)
|
||||
{
|
||||
Debug.Assert(transportFactory != null);
|
||||
Debug.Assert(endPointInformation != null);
|
||||
Debug.Assert(endPointInformation.Type == ListenType.IPEndPoint);
|
||||
Debug.Assert(handler != null);
|
||||
|
||||
_transportFactory = transportFactory;
|
||||
_endPointInformation = endPointInformation;
|
||||
_handler = handler;
|
||||
|
||||
_listenSocket = null;
|
||||
_listenTask = null;
|
||||
}
|
||||
|
||||
public Task BindAsync()
|
||||
{
|
||||
if (_listenSocket != null)
|
||||
{
|
||||
throw new InvalidOperationException("Transport is already bound");
|
||||
}
|
||||
|
||||
IPEndPoint endPoint = _endPointInformation.IPEndPoint;
|
||||
|
||||
var listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
// Kestrel expects IPv6Any to bind to both IPv6 and IPv4
|
||||
if (endPoint.Address == IPAddress.IPv6Any)
|
||||
{
|
||||
listenSocket.DualMode = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
listenSocket.Bind(endPoint);
|
||||
}
|
||||
catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse)
|
||||
{
|
||||
throw new AddressInUseException(e.Message, e);
|
||||
}
|
||||
|
||||
// If requested port was "0", replace with assigned dynamic port.
|
||||
if (_endPointInformation.IPEndPoint.Port == 0)
|
||||
{
|
||||
_endPointInformation.IPEndPoint = (IPEndPoint)listenSocket.LocalEndPoint;
|
||||
}
|
||||
|
||||
listenSocket.Listen(512);
|
||||
|
||||
_listenSocket = listenSocket;
|
||||
|
||||
_listenTask = Task.Run(() => RunAcceptLoopAsync());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task UnbindAsync()
|
||||
{
|
||||
if (_listenSocket != null)
|
||||
{
|
||||
var listenSocket = _listenSocket;
|
||||
_listenSocket = null;
|
||||
|
||||
listenSocket.Dispose();
|
||||
|
||||
Debug.Assert(_listenTask != null);
|
||||
await _listenTask;
|
||||
_listenTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RunAcceptLoopAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Socket acceptSocket = await _listenSocket.AcceptAsync();
|
||||
|
||||
acceptSocket.NoDelay = _endPointInformation.NoDelay;
|
||||
|
||||
SocketConnection connection = new SocketConnection(acceptSocket, this);
|
||||
connection.Start(_handler);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (_listenSocket == null)
|
||||
{
|
||||
// Means we must be unbinding. Eat the exception.
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal SocketTransportFactory TransportFactory => _transportFactory;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
|
||||
{
|
||||
public sealed class SocketTransportFactory : ITransportFactory
|
||||
{
|
||||
private readonly PipeFactory _pipeFactory = new PipeFactory();
|
||||
private readonly bool _forceDispatch;
|
||||
|
||||
public SocketTransportFactory(bool forceDispatch = false)
|
||||
{
|
||||
_forceDispatch = forceDispatch;
|
||||
}
|
||||
|
||||
public ITransport Create(IEndPointInformation endPointInformation, IConnectionHandler handler)
|
||||
{
|
||||
if (endPointInformation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endPointInformation));
|
||||
}
|
||||
|
||||
if (endPointInformation.Type != ListenType.IPEndPoint)
|
||||
{
|
||||
throw new ArgumentException("Only ListenType.IPEndPoint is supported", nameof(endPointInformation));
|
||||
}
|
||||
|
||||
if (handler == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
}
|
||||
|
||||
return new SocketTransport(this, endPointInformation, handler);
|
||||
}
|
||||
|
||||
internal PipeFactory PipeFactory => _pipeFactory;
|
||||
|
||||
internal bool ForceDispatch => _forceDispatch;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
public static class WebHostBuilderSocketExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Specify Sockets as the transport to be used by Kestrel.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">
|
||||
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder.
|
||||
/// </returns>
|
||||
public static IWebHostBuilder UseSockets(this IWebHostBuilder hostBuilder)
|
||||
{
|
||||
return hostBuilder.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<ITransportFactory, SocketTransportFactory>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Server.Kestrel.Core\Microsoft.AspNetCore.Server.Kestrel.Core.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -813,6 +813,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
public IPEndPoint RemoteEndPoint { get; }
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
public PipeFactory PipeFactory { get; }
|
||||
public bool RequiresDispatch { get; }
|
||||
public IScheduler InputWriterScheduler { get; }
|
||||
public IScheduler OutputReaderScheduler { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
var connectionHandler = new ConnectionHandler<object>(listenOptions: null, serviceContext: serviceContext, application: null);
|
||||
var mockScheduler = Mock.Of<IScheduler>();
|
||||
var outputPipeOptions = connectionHandler.GetOutputPipeOptions(mockScheduler);
|
||||
var outputPipeOptions = connectionHandler.GetOutputPipeOptions(requiresDispatch: true, readerScheduler: mockScheduler);
|
||||
|
||||
Assert.Equal(expectedMaximumSizeLow, outputPipeOptions.MaximumSizeLow);
|
||||
Assert.Equal(expectedMaximumSizeHigh, outputPipeOptions.MaximumSizeHigh);
|
||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
var connectionHandler = new ConnectionHandler<object>(listenOptions: null, serviceContext: serviceContext, application: null);
|
||||
var mockScheduler = Mock.Of<IScheduler>();
|
||||
var inputPipeOptions = connectionHandler.GetInputPipeOptions(mockScheduler);
|
||||
var inputPipeOptions = connectionHandler.GetInputPipeOptions(requiresDispatch: true, writerScheduler: mockScheduler);
|
||||
|
||||
Assert.Equal(expectedMaximumSizeLow, inputPipeOptions.MaximumSizeLow);
|
||||
Assert.Equal(expectedMaximumSizeHigh, inputPipeOptions.MaximumSizeHigh);
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ using System.Net.Sockets;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||
{
|
||||
|
|
@ -18,7 +20,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
/// </summary>
|
||||
public class TestServer : IDisposable
|
||||
{
|
||||
private LibuvTransport _transport;
|
||||
private ITransport _transport;
|
||||
private ListenOptions _listenOptions;
|
||||
|
||||
public TestServer(RequestDelegate app)
|
||||
|
|
@ -46,37 +48,55 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
_listenOptions = listenOptions;
|
||||
|
||||
Context = context;
|
||||
TransportContext = new LibuvTransportContext()
|
||||
{
|
||||
AppLifetime = new LifetimeNotImplemented(),
|
||||
ConnectionHandler = new ConnectionHandler<HttpContext>(listenOptions, Context, new DummyApplication(app, httpContextFactory)),
|
||||
Log = new LibuvTrace(new TestApplicationErrorLogger()),
|
||||
Options = new LibuvTransportOptions()
|
||||
{
|
||||
ThreadCount = 1,
|
||||
ShutdownTimeout = TimeSpan.FromSeconds(5)
|
||||
}
|
||||
};
|
||||
|
||||
_transport = s_transportFactory.Create(listenOptions, new ConnectionHandler<HttpContext>(listenOptions, context, new DummyApplication(app, httpContextFactory)));
|
||||
|
||||
try
|
||||
{
|
||||
_transport = new LibuvTransport(TransportContext, _listenOptions);
|
||||
_transport.BindAsync().Wait();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_transport.UnbindAsync().Wait();
|
||||
_transport.StopAsync().Wait();
|
||||
_transport = null;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Switch this to test on socket transport
|
||||
private static readonly ITransportFactory s_transportFactory = CreateLibuvTransportFactory();
|
||||
// private static readonly ITransportFactory s_transportFactory = CreateSocketTransportFactory();
|
||||
|
||||
private static ITransportFactory CreateLibuvTransportFactory()
|
||||
{
|
||||
var transportOptions = new LibuvTransportOptions()
|
||||
{
|
||||
ThreadCount = 1,
|
||||
ShutdownTimeout = TimeSpan.FromSeconds(5)
|
||||
};
|
||||
|
||||
var transportFactory = new LibuvTransportFactory(
|
||||
Options.Create(transportOptions),
|
||||
new LifetimeNotImplemented(),
|
||||
new KestrelTestLoggerFactory(new TestApplicationErrorLogger()));
|
||||
|
||||
return transportFactory;
|
||||
}
|
||||
|
||||
private static ITransportFactory CreateSocketTransportFactory()
|
||||
{
|
||||
// For now, force the socket transport to do threadpool dispatch for tests.
|
||||
// There are a handful of tests that deadlock due to test issues if we don't do dispatch.
|
||||
// We should clean these up, but for now, make them work by forcing dispatch.
|
||||
return new SocketTransportFactory(true);
|
||||
}
|
||||
|
||||
public IPEndPoint EndPoint => _listenOptions.IPEndPoint;
|
||||
public int Port => _listenOptions.IPEndPoint.Port;
|
||||
public AddressFamily AddressFamily => _listenOptions.IPEndPoint.AddressFamily;
|
||||
|
||||
public TestServiceContext Context { get; }
|
||||
public LibuvTransportContext TransportContext { get; }
|
||||
|
||||
public TestConnection CreateConnection()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
public PipeFactory PipeFactory { get; }
|
||||
public bool RequiresDispatch { get; }
|
||||
public IScheduler InputWriterScheduler { get; }
|
||||
public IScheduler OutputReaderScheduler { get; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue