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

Commit

Permalink
Transport agnostic kestrel refactoring (#1551)
Browse files Browse the repository at this point in the history
- Add transport interfaces
- Create separate Core and Libuv projects

#828
  • Loading branch information
halter73 authored Mar 29, 2017
1 parent 792b71d commit 7f78558
Show file tree
Hide file tree
Showing 191 changed files with 2,592 additions and 28,184 deletions.
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 =>
{
// 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

0 comments on commit 7f78558

Please sign in to comment.