diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/ListenerPrimary.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/ListenerPrimary.cs index f2d9c2f3a9..4f5bb907fb 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/ListenerPrimary.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/ListenerPrimary.cs @@ -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 { /// - /// 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. /// public abstract class ListenerPrimary : Listener @@ -19,8 +20,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http private readonly List _dispatchPipes = new List(); 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> _dummyMessage = new ArraySegment>(new[] { new ArraySegment(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(), 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 =>