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

Commit 3451362

Browse files
committed
Rio Transport on WinThreadpool
1 parent 6a403d2 commit 3451362

28 files changed

+2476
-0
lines changed

KestrelHttpServer.sln

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server
7070
EndProject
7171
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Tests", "test\Microsoft.AspNetCore.Server.Kestrel.Tests\Microsoft.AspNetCore.Server.Kestrel.Tests.csproj", "{4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC}"
7272
EndProject
73+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransportChoiceApp", "samples\TransportChoiceApp\TransportChoiceApp.csproj", "{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}"
74+
EndProject
75+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.WindowsRio", "src\Microsoft.AspNetCore.Server.Kestrel.Transport.WindowsRio\Microsoft.AspNetCore.Server.Kestrel.Transport.WindowsRio.csproj", "{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}"
76+
EndProject
7377
Global
7478
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7579
Debug|Any CPU = Debug|Any CPU
@@ -248,6 +252,30 @@ Global
248252
{4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC}.Release|x64.Build.0 = Release|Any CPU
249253
{4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC}.Release|x86.ActiveCfg = Release|Any CPU
250254
{4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC}.Release|x86.Build.0 = Release|Any CPU
255+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
256+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Debug|Any CPU.Build.0 = Debug|Any CPU
257+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Debug|x64.ActiveCfg = Debug|Any CPU
258+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Debug|x64.Build.0 = Debug|Any CPU
259+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Debug|x86.ActiveCfg = Debug|Any CPU
260+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Debug|x86.Build.0 = Debug|Any CPU
261+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Release|Any CPU.ActiveCfg = Release|Any CPU
262+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Release|Any CPU.Build.0 = Release|Any CPU
263+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Release|x64.ActiveCfg = Release|Any CPU
264+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Release|x64.Build.0 = Release|Any CPU
265+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Release|x86.ActiveCfg = Release|Any CPU
266+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39}.Release|x86.Build.0 = Release|Any CPU
267+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
268+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
269+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Debug|x64.ActiveCfg = Debug|Any CPU
270+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Debug|x64.Build.0 = Debug|Any CPU
271+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Debug|x86.ActiveCfg = Debug|Any CPU
272+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Debug|x86.Build.0 = Debug|Any CPU
273+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
274+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Release|Any CPU.Build.0 = Release|Any CPU
275+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Release|x64.ActiveCfg = Release|Any CPU
276+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Release|x64.Build.0 = Release|Any CPU
277+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Release|x86.ActiveCfg = Release|Any CPU
278+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1}.Release|x86.Build.0 = Release|Any CPU
251279
EndGlobalSection
252280
GlobalSection(SolutionProperties) = preSolution
253281
HideSolutionNode = FALSE
@@ -269,5 +297,7 @@ Global
269297
{2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
270298
{D95A7EC3-48AC-4D03-B2E2-0DA3E13BD3A4} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
271299
{4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
300+
{DD57C592-6BCC-4A06-B4C7-0D23A286DFB1} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
301+
{18BE39D5-C307-4F4D-BBB8-082BFAE8AF39} = {8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE}
272302
EndGlobalSection
273303
EndGlobal

samples/TransportChoiceApp/Startup.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.Net;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Builder;
10+
using Microsoft.AspNetCore.Hosting;
11+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
12+
13+
namespace TransportChoiceApp
14+
{
15+
public class Startup
16+
{
17+
private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
18+
19+
public void Configure(IApplicationBuilder app)
20+
{
21+
app.Run((httpContext) =>
22+
{
23+
var response = httpContext.Response;
24+
var payloadLength = _helloWorldPayload.Length;
25+
response.StatusCode = 200;
26+
response.ContentType = "text/plain";
27+
response.ContentLength = payloadLength;
28+
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
29+
});
30+
}
31+
32+
public static void Main(string[] args)
33+
{
34+
var hostBuilder = new WebHostBuilder()
35+
.UseKestrel(options =>
36+
{
37+
options.Listen(IPAddress.Loopback, 5001);
38+
39+
});
40+
41+
Console.WriteLine(@"
42+
Choose a Transport
43+
1. Libuv
44+
2. Sockets
45+
3. Rio
46+
");
47+
48+
switch (Console.ReadKey().KeyChar)
49+
{
50+
case '1':
51+
hostBuilder.UseLibuv(options =>
52+
{
53+
//options.ThreadCount = 4;
54+
});
55+
break;
56+
case '2':
57+
hostBuilder.UseSockets();
58+
break;
59+
case '3':
60+
hostBuilder.UseWindowsRio();
61+
break;
62+
default:
63+
Console.WriteLine("Invalid option");
64+
return;
65+
}
66+
67+
Console.WriteLine();
68+
69+
hostBuilder
70+
.UseContentRoot(Directory.GetCurrentDirectory())
71+
.UseStartup<Startup>();
72+
73+
var host = hostBuilder.Build();
74+
75+
host.Run();
76+
}
77+
}
78+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<Import Project="..\..\build\common.props" />
4+
5+
<PropertyGroup>
6+
<TargetFramework>netcoreapp2.0</TargetFramework>
7+
<IsPackable>false</IsPackable>
8+
<OutputType>exe</OutputType>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.Kestrel.Transport.WindowsRio\Microsoft.AspNetCore.Server.Kestrel.Transport.WindowsRio.csproj" />
13+
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.Kestrel\Microsoft.AspNetCore.Server.Kestrel.csproj" />
14+
</ItemGroup>
15+
16+
</Project>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
9+
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.WindowsRio.Internal
10+
{
11+
/// <summary>
12+
/// Simple awaitable gate - intended to synchronize a single producer with a single consumer to ensure the producer doesn't
13+
/// produce until the consumer is ready. Similar to a <see cref="TaskCompletionSource{TResult}"/> but reusable so we don't have
14+
/// to keep allocating new ones every time.
15+
/// </summary>
16+
/// <remarks>
17+
/// The gate can be in one of two states: "Open", indicating that an await will immediately return and "Closed", meaning that an await
18+
/// will block until the gate is opened. The gate is initially "Closed" and can be opened by a call to <see cref="Open"/>. Upon the completion
19+
/// of an await, it will automatically return to the "Closed" state (this is done in the <see cref="GetResult"/> call that is injected by the
20+
/// compiler's async/await logic).
21+
/// </remarks>
22+
public class AutoResetGate<T> : ICriticalNotifyCompletion
23+
{
24+
private static readonly Action _gateIsOpen = () => { };
25+
26+
private Action _gateState;
27+
private T _value;
28+
29+
/// <summary>
30+
/// Returns a boolean indicating if the gate is "open"
31+
/// </summary>
32+
public bool IsCompleted => ReferenceEquals(_gateState, _gateIsOpen);
33+
34+
public void UnsafeOnCompleted(Action continuation) => OnCompleted(continuation);
35+
36+
public void OnCompleted(Action continuation)
37+
{
38+
// If we're already completed, call the continuation immediately
39+
if (IsCompleted)
40+
{
41+
continuation();
42+
}
43+
else
44+
{
45+
// Otherwise, if the current continuation is null, atomically store the new continuation in the field and return the old value
46+
var previous = Interlocked.CompareExchange(ref _gateState, continuation, null);
47+
if (ReferenceEquals(previous, _gateIsOpen))
48+
{
49+
// It got completed in the time between the previous the method and the cmpexch.
50+
// So call the continuation (the value of _continuation will remain _completed because cmpexch is atomic,
51+
// so we didn't accidentally replace it).
52+
continuation();
53+
}
54+
}
55+
}
56+
57+
/// <summary>
58+
/// Resets the gate to continue blocking the waiter. This is called immediately after awaiting the signal.
59+
/// </summary>
60+
public T GetResult()
61+
{
62+
var value = _value;
63+
_value = default(T);
64+
65+
// Clear the active continuation to "reset" the state of this event
66+
_gateState = null;
67+
68+
return value;
69+
}
70+
71+
/// <summary>
72+
/// Set the gate to allow the waiter to continue.
73+
/// </summary>
74+
public void Open(T value)
75+
{
76+
// Set the stored continuation value to a sentinel that indicates the state is completed, then call the previous value.
77+
_value = value;
78+
var completion = Interlocked.Exchange(ref _gateState, _gateIsOpen);
79+
if (!ReferenceEquals(completion, _gateIsOpen))
80+
{
81+
completion?.Invoke();
82+
}
83+
}
84+
85+
private void Close()
86+
{
87+
// Clear the active continuation to "reset" the state of this event
88+
_gateState = null;
89+
}
90+
91+
public AutoResetGate<T> GetAwaiter() => this;
92+
93+
private void ThrowMultiConsumerUseError()
94+
{
95+
throw new InvalidOperationException();
96+
}
97+
}
98+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.Buffers;
6+
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
7+
8+
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.WindowsRio.Internal
9+
{
10+
public class BufferMapper
11+
{
12+
private readonly object _mappingSync = new object();
13+
private BufferMapping[] _bufferIdMappings = new BufferMapping[0];
14+
15+
public BufferMapper(MemoryPool memoryPool)
16+
{
17+
memoryPool.RegisterSlabAllocationCallback((slab) => OnSlabAllocated(slab));
18+
memoryPool.RegisterSlabDeallocationCallback((slab) => OnSlabDeallocated(slab));
19+
}
20+
21+
internal RioRegisteredBuffer GetRegisteredBuffer(IntPtr address, out long startAddress)
22+
{
23+
var buffer = default(RioRegisteredBuffer);
24+
startAddress = 0;
25+
26+
// Take local copy to avoid modifications
27+
var bufferIdMappings = _bufferIdMappings;
28+
var addressLong = address.ToInt64();
29+
30+
// Can binary search if it's too slow
31+
for (var i = 0; i < bufferIdMappings.Length; i++)
32+
{
33+
var mapping = bufferIdMappings[i];
34+
if (addressLong >= mapping.Start && addressLong <= mapping.End)
35+
{
36+
buffer = mapping.Buffer;
37+
startAddress = mapping.Start;
38+
break;
39+
}
40+
}
41+
42+
return buffer;
43+
}
44+
45+
internal unsafe RioBufferSegment GetSegmentFromBuffer(Buffer<byte> buffer)
46+
{
47+
// It's ok to unpin the handle here because the memory is from the pool
48+
// we created, which is already pinned.
49+
var pin = buffer.Pin();
50+
var spanPtr = (IntPtr)pin.PinnedPointer;
51+
pin.Free();
52+
53+
long startAddress;
54+
long spanAddress = spanPtr.ToInt64();
55+
var bufferId = GetRegisteredBuffer(spanPtr, out startAddress);
56+
57+
checked
58+
{
59+
var offset = unchecked((uint)(spanAddress - startAddress));
60+
return new RioBufferSegment(bufferId, offset, (uint)buffer.Length);
61+
}
62+
}
63+
64+
private void OnSlabAllocated(MemoryPoolSlab slab)
65+
{
66+
var memoryPtr = slab.NativePointer;
67+
var buffer = RioRegisteredBuffer.Create(memoryPtr, (uint)slab.Length);
68+
var addressLong = memoryPtr.ToInt64();
69+
70+
// Read, write and swap the mappings under lock
71+
lock (_mappingSync)
72+
{
73+
var currentMappings = _bufferIdMappings;
74+
var newMappings = new BufferMapping[currentMappings.Length + 1];
75+
76+
for (var i = 0; i < currentMappings.Length; i++)
77+
{
78+
newMappings[i] = currentMappings[i];
79+
}
80+
81+
newMappings[currentMappings.Length] = new BufferMapping
82+
{
83+
Buffer = buffer,
84+
Start = addressLong,
85+
End = addressLong + slab.Length
86+
};
87+
88+
_bufferIdMappings = newMappings;
89+
}
90+
}
91+
92+
private void OnSlabDeallocated(MemoryPoolSlab slab)
93+
{
94+
var memoryPtr = slab.NativePointer;
95+
var addressLong = memoryPtr.ToInt64();
96+
97+
// Read, write and swap the mappings under lock
98+
lock (_mappingSync)
99+
{
100+
var currentMappings = _bufferIdMappings;
101+
var newMappings = new BufferMapping[currentMappings.Length - 1];
102+
103+
for (int i = 0, n = 0; i < currentMappings.Length; i++)
104+
{
105+
var bufferMapping = currentMappings[i];
106+
if (addressLong != bufferMapping.Start)
107+
{
108+
// Not being removed, add it to new mappings
109+
newMappings[n] = currentMappings[i];
110+
n++;
111+
}
112+
else
113+
{
114+
// Being removed, dispose, don't add
115+
bufferMapping.Buffer.Dispose();
116+
}
117+
}
118+
119+
_bufferIdMappings = newMappings;
120+
}
121+
}
122+
123+
private struct BufferMapping
124+
{
125+
public RioRegisteredBuffer Buffer;
126+
public long Start;
127+
public long End;
128+
129+
public override string ToString()
130+
{
131+
return $"{Buffer} ({Start}) - ({End})";
132+
}
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)