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:
Geoff Kizer 2017-04-13 23:10:06 -07:00 committed by David Fowler
parent 0723d46ec4
commit efa0a48fb1
14 changed files with 505 additions and 23 deletions

View File

@ -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}

View File

@ -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()
};

View File

@ -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; }
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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>();
});
}
}
}

View File

@ -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>

View File

@ -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; }
}

View File

@ -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);

View File

@ -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()
{

View File

@ -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; }
}