Use IOCP on secondary listener threads on Windows

Addresses #679
This commit is contained in:
moozzyk 2016-03-25 09:48:29 -07:00
parent ebca8db7dc
commit 75adbc18a2
1 changed files with 61 additions and 3 deletions

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Networking;
@ -11,7 +12,7 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Http
{
/// <summary>
/// A primary listener waits for incoming connections on a specified socket. Incoming
/// A primary listener waits for incoming connections on a specified socket. Incoming
/// connections may be passed to a secondary listener to handle.
/// </summary>
public abstract class ListenerPrimary : Listener
@ -19,8 +20,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private readonly List<UvPipeHandle> _dispatchPipes = new List<UvPipeHandle>();
private int _dispatchIndex;
private string _pipeName;
private IntPtr _fileCompletionInfoPtr;
private bool _tryDetachFromIOCP = PlatformApis.IsWindows;
// this message is passed to write2 because it must be non-zero-length,
// this message is passed to write2 because it must be non-zero-length,
// but it has no other functional significance
private readonly ArraySegment<ArraySegment<byte>> _dummyMessage = new ArraySegment<ArraySegment<byte>>(new[] { new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 }) });
@ -37,6 +40,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{
_pipeName = pipeName;
if (_fileCompletionInfoPtr == IntPtr.Zero)
{
var fileCompletionInfo = new FILE_COMPLETION_INFORMATION() { Key = IntPtr.Zero, Port = IntPtr.Zero };
_fileCompletionInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(fileCompletionInfo));
Marshal.StructureToPtr(fileCompletionInfo, _fileCompletionInfoPtr, false);
}
await StartAsync(address, thread).ConfigureAwait(false);
await Thread.PostAsync(state => ((ListenerPrimary)state).PostCallback(),
@ -85,6 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}
else
{
DetachFromIOCP(socket);
var dispatchPipe = _dispatchPipes[index];
var write = new UvWriteReq(Log);
write.Init(Thread.Loop);
@ -92,7 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
dispatchPipe,
_dummyMessage,
socket,
(write2, status, error, state) =>
(write2, status, error, state) =>
{
write2.Dispose();
((UvStreamHandle)state).Dispose();
@ -101,12 +112,59 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}
}
private void DetachFromIOCP(UvHandle handle)
{
if (!_tryDetachFromIOCP)
{
return;
}
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff728840(v=vs.85).aspx
const int FileReplaceCompletionInformation = 61;
// https://msdn.microsoft.com/en-us/library/cc704588.aspx
const uint STATUS_INVALID_INFO_CLASS = 0xC0000003;
var statusBlock = new IO_STATUS_BLOCK();
var socket = IntPtr.Zero;
Thread.Loop.Libuv.uv_fileno(handle, ref socket);
if (NtSetInformationFile(socket, out statusBlock, _fileCompletionInfoPtr,
(uint)Marshal.SizeOf<FILE_COMPLETION_INFORMATION>(), FileReplaceCompletionInformation) == STATUS_INVALID_INFO_CLASS)
{
// Replacing IOCP information is only supported on Windows 8.1 or newer
_tryDetachFromIOCP = false;
}
}
private struct IO_STATUS_BLOCK
{
uint status;
ulong information;
}
private struct FILE_COMPLETION_INFORMATION
{
public IntPtr Port;
public IntPtr Key;
}
[DllImport("NtDll.dll")]
private static extern uint NtSetInformationFile(IntPtr FileHandle,
out IO_STATUS_BLOCK IoStatusBlock, IntPtr FileInformation, uint Length,
int FileInformationClass);
public override async Task DisposeAsync()
{
// Call base first so the ListenSocket gets closed and doesn't
// try to dispatch connections to closed pipes.
await base.DisposeAsync().ConfigureAwait(false);
if (_fileCompletionInfoPtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(_fileCompletionInfoPtr);
_fileCompletionInfoPtr = IntPtr.Zero;
}
if (Thread.FatalError == null && ListenPipe != null)
{
await Thread.PostAsync(state =>