Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Transport agnostic kestrel refactoring #1551

Merged
merged 20 commits into from
Mar 29, 2017
34 changes: 32 additions & 2 deletions KestrelHttpServer.sln
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.4
VisualStudioVersion = 15.0.26228.9
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
ProjectSection(SolutionItems) = preProject
Expand Down Expand Up @@ -40,7 +40,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{0EF2AC
test\shared\TestServiceContext.cs = test\shared\TestServiceContext.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel", "src\Microsoft.AspNetCore.Server.Kestrel\Microsoft.AspNetCore.Server.Kestrel.csproj", "{F510611A-3BEE-4B88-A613-5F4A74ED82A1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Core", "src\Microsoft.AspNetCore.Server.Kestrel.Core\Microsoft.AspNetCore.Server.Kestrel.Core.csproj", "{F510611A-3BEE-4B88-A613-5F4A74ED82A1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.KestrelTests", "test\Microsoft.AspNetCore.Server.KestrelTests\Microsoft.AspNetCore.Server.KestrelTests.csproj", "{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}"
EndProject
Expand All @@ -61,6 +61,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestResources", "TestResour
test\shared\TestResources\testCert.pfx = test\shared\TestResources\testCert.pfx
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv", "src\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj", "{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}"
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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -167,6 +171,30 @@ Global
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x64.Build.0 = Release|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x86.ActiveCfg = Release|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x86.Build.0 = Release|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|x64.ActiveCfg = Debug|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|x64.Build.0 = Debug|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|x86.ActiveCfg = Debug|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|x86.Build.0 = Debug|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|Any CPU.Build.0 = Release|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|x64.ActiveCfg = Release|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|x64.Build.0 = Release|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|x86.ActiveCfg = Release|Any CPU
{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|x86.Build.0 = Release|Any CPU
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|x64.ActiveCfg = Debug|Any CPU
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|x64.Build.0 = Debug|Any CPU
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|x86.ActiveCfg = Debug|Any CPU
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|x86.Build.0 = Debug|Any CPU
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|Any CPU.Build.0 = Release|Any CPU
{56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x64.ActiveCfg = Release|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -182,5 +210,7 @@ Global
{9559A5F1-080C-4909-B6CF-7E4B3DC55748} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{EBFE9719-A44B-4978-A71F-D5C254E7F35A} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{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}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions samples/SampleApp/SampleApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.Kestrel\Microsoft.AspNetCore.Server.Kestrel.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.Kestrel.Https\Microsoft.AspNetCore.Server.Kestrel.Https.csproj" />
</ItemGroup>

Expand Down
6 changes: 4 additions & 2 deletions samples/SampleApp/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ public static void Main(string[] args)

// The following section should be used to demo sockets
//options.ListenUnixSocket("/tmp/kestrel-test.sock");

})
.UseLibuv(options =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird to have this on the same level as kestrel. This needs to move to options.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That just means that hosting services don't get injected into the ITransportFactory for free. For example, LibuvTransportFactory depends on IOptions<LibuvTransportOptions>, IApplicationLifetimeandILoggerFactory`.

Do you want to use ActivatorUtilities to activate a registered ITransportFactory type on KestrelServerOptions? That seems really gross.

{
// Uncomment the following line to change the default number of libuv threads for all endpoints.
//options.ThreadCount = 4;
options.ThreadCount = 4;
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// 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 System.IO;
using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Server.Kestrel.Adapter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
{
public class AdaptedPipeline : IDisposable
public class AdaptedPipeline
{
private const int MinAllocBufferSize = 2048;

Expand All @@ -31,30 +31,16 @@ public AdaptedPipeline(

public ISocketOutput Output => _output;

public void Dispose()
{
Input.Writer.Complete();
}

public async Task StartAsync()
public async Task RunAsync()
{
var inputTask = ReadInputAsync();
var outputTask = _output.WriteOutputAsync();

var result = await Task.WhenAny(inputTask, outputTask);
await inputTask;

if (result == inputTask)
{
// Close output
_output.Dispose();
}
else
{
// Close input
Input.Writer.Complete();
}
_output.Dispose();

await Task.WhenAll(inputTask, outputTask);
await outputTask;
}

private async Task ReadInputAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class StreamSocketOutput : ISocketOutput

private readonly Stream _outputStream;
private readonly IPipe _pipe;
private object _sync = new object();
private readonly object _sync = new object();
private bool _completed;

public StreamSocketOutput(Stream outputStream, IPipe pipe)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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 System;
using System.IO.Pipelines;
using System.Threading;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Transport;

namespace Microsoft.AspNetCore.Server.Kestrel.Internal
{
public class ConnectionHandler<TContext> : IConnectionHandler
{
// Base32 encoding - in ascii sort order for easy text based sorting
private static readonly string _encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV";

// Seed the _lastConnectionId for this application instance with
// the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001
// for a roughly increasing _requestId over restarts
private static long _lastConnectionId = DateTime.UtcNow.Ticks;

private readonly ServiceContext _serviceContext;
private readonly IHttpApplication<TContext> _application;

public ConnectionHandler(ServiceContext serviceContext, IHttpApplication<TContext> application)
{
_serviceContext = serviceContext;
_application = application;
}

public IConnectionContext OnConnection(IConnectionInformation connectionInfo)
{
var inputPipe = connectionInfo.PipeFactory.Create(GetInputPipeOptions(connectionInfo.InputWriterScheduler));
var outputPipe = connectionInfo.PipeFactory.Create(GetOutputPipeOptions(connectionInfo.OutputWriterScheduler));

var connectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId));

var frameContext = new FrameContext
{
ConnectionId = connectionId,
ConnectionInformation = connectionInfo,
ServiceContext = _serviceContext
};

// TODO: Untangle this mess
var frame = new Frame<TContext>(_application, frameContext);
var outputProducer = new SocketOutputProducer(outputPipe.Writer, frame, connectionId, _serviceContext.Log);
frame.LifetimeControl = new ConnectionLifetimeControl(connectionId, outputPipe.Reader, outputProducer, _serviceContext.Log);

var connection = new FrameConnection(new FrameConnectionContext
{
ConnectionId = connectionId,
ServiceContext = _serviceContext,
PipeFactory = connectionInfo.PipeFactory,
ConnectionAdapters = connectionInfo.ListenOptions.ConnectionAdapters,
Frame = frame,
Input = inputPipe,
Output = outputPipe,
OutputProducer = outputProducer
});

// Since data cannot be added to the inputPipe by the transport until OnConnection returns,
// Frame.RequestProcessingAsync is guaranteed to unblock the transport thread before calling
// application code.
connection.StartRequestProcessing();

return connection;
}

// Internal for testing
internal PipeOptions GetInputPipeOptions(IScheduler writerScheduler) => new PipeOptions
{
ReaderScheduler = _serviceContext.ThreadPool,
WriterScheduler = writerScheduler,
MaximumSizeHigh = _serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0,
MaximumSizeLow = _serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0
};

internal PipeOptions GetOutputPipeOptions(IScheduler readerScheduler) => new PipeOptions
{
ReaderScheduler = readerScheduler,
WriterScheduler = _serviceContext.ThreadPool,
MaximumSizeHigh = GetOutputResponseBufferSize(),
MaximumSizeLow = GetOutputResponseBufferSize()
};

private long GetOutputResponseBufferSize()
{
var bufferSize = _serviceContext.ServerOptions.Limits.MaxResponseBufferSize;
if (bufferSize == 0)
{
// 0 = no buffering so we need to configure the pipe so the the writer waits on the reader directly
return 1;
}

// null means that we have no back pressure
return bufferSize ?? 0;
}

private static unsafe string GenerateConnectionId(long id)
{
// The following routine is ~310% faster than calling long.ToString() on x64
// and ~600% faster than calling long.ToString() on x86 in tight loops of 1 million+ iterations
// See: https://github.com/aspnet/Hosting/pull/385

// stackalloc to allocate array on stack rather than heap
char* charBuffer = stackalloc char[13];

charBuffer[0] = _encode32Chars[(int)(id >> 60) & 31];
charBuffer[1] = _encode32Chars[(int)(id >> 55) & 31];
charBuffer[2] = _encode32Chars[(int)(id >> 50) & 31];
charBuffer[3] = _encode32Chars[(int)(id >> 45) & 31];
charBuffer[4] = _encode32Chars[(int)(id >> 40) & 31];
charBuffer[5] = _encode32Chars[(int)(id >> 35) & 31];
charBuffer[6] = _encode32Chars[(int)(id >> 30) & 31];
charBuffer[7] = _encode32Chars[(int)(id >> 25) & 31];
charBuffer[8] = _encode32Chars[(int)(id >> 20) & 31];
charBuffer[9] = _encode32Chars[(int)(id >> 15) & 31];
charBuffer[10] = _encode32Chars[(int)(id >> 10) & 31];
charBuffer[11] = _encode32Chars[(int)(id >> 5) & 31];
charBuffer[12] = _encode32Chars[(int)id & 31];

// string ctor overload that takes char*
return new string(charBuffer, 0, 13);
}
}
}
Loading