diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Networking/Libuv.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Networking/Libuv.cs index 279d4885cf..a7022d8fc4 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Networking/Libuv.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Networking/Libuv.cs @@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking _uv_stop = NativeDarwinMonoMethods.uv_stop; _uv_ref = NativeDarwinMonoMethods.uv_ref; _uv_unref = NativeDarwinMonoMethods.uv_unref; + _uv_fileno = NativeDarwinMonoMethods.uv_fileno; _uv_close = NativeDarwinMonoMethods.uv_close; _uv_async_init = NativeDarwinMonoMethods.uv_async_init; _uv_async_send = NativeDarwinMonoMethods.uv_async_send; @@ -68,6 +69,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking _uv_stop = NativeMethods.uv_stop; _uv_ref = NativeMethods.uv_ref; _uv_unref = NativeMethods.uv_unref; + _uv_fileno = NativeMethods.uv_fileno; _uv_close = NativeMethods.uv_close; _uv_async_init = NativeMethods.uv_async_init; _uv_async_send = NativeMethods.uv_async_send; @@ -177,6 +179,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking _uv_unref(handle); } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + protected delegate int uv_fileno_func(UvHandle handle, ref IntPtr socket); + protected uv_fileno_func _uv_fileno; + public int uv_fileno(UvHandle handle, ref IntPtr socket) + { + handle.Validate(); + return Check(_uv_fileno(handle, ref socket)); + } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void uv_close_cb(IntPtr handle); protected Action _uv_close; @@ -221,6 +232,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking { handle.Validate(); Check(_uv_tcp_bind(handle, ref addr, flags)); + if (PlatformApis.IsWindows) + { + tcp_bind_windows_extras(handle); + } + } + + private unsafe void tcp_bind_windows_extras(UvTcpHandle handle) + { + const int SIO_LOOPBACK_FAST_PATH = -1744830448; // IOC_IN | IOC_WS2 | 16; + const int WSAEOPNOTSUPP = 10000 + 45; // (WSABASEERR+45) + const int SOCKET_ERROR = -1; + + var socket = IntPtr.Zero; + Check(_uv_fileno(handle, ref socket)); + + // Enable loopback fast-path for lower latency for localhost comms, like HttpPlatformHandler fronting + // http://blogs.technet.com/b/wincat/archive/2012/12/05/fast-tcp-loopback-performance-and-low-latency-with-windows-server-2012-tcp-loopback-fast-path.aspx + // https://github.com/libuv/libuv/issues/489 + var optionValue = 1; + uint dwBytes = 0u; + + var result = NativeMethods.WSAIoctl(socket, SIO_LOOPBACK_FAST_PATH, &optionValue, sizeof(int), null, 0, out dwBytes, IntPtr.Zero, IntPtr.Zero); + if (result == SOCKET_ERROR) + { + var errorId = NativeMethods.WSAGetLastError(); + if (errorId == WSAEOPNOTSUPP) + { + // This system is not >= Windows Server 2012, and the call is not supported. + } + else + { + Check(errorId); + } + } } protected Func _uv_tcp_open; @@ -501,6 +546,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)] public static extern void uv_unref(UvHandle handle); + [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)] + public static extern int uv_fileno(UvHandle handle, ref IntPtr socket); + [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)] public static extern void uv_close(IntPtr handle, uv_close_cb close_cb); @@ -587,6 +635,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)] unsafe public static extern int uv_walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg); + + [DllImport("WS2_32.dll", CallingConvention = CallingConvention.Winapi)] + unsafe public static extern int WSAIoctl( + IntPtr socket, + int dwIoControlCode, + int* lpvInBuffer, + uint cbInBuffer, + int* lpvOutBuffer, + int cbOutBuffer, + out uint lpcbBytesReturned, + IntPtr lpOverlapped, + IntPtr lpCompletionRoutine + ); + + [DllImport("WS2_32.dll", CallingConvention = CallingConvention.Winapi)] + public static extern int WSAGetLastError(); } private static class NativeDarwinMonoMethods @@ -609,6 +673,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)] public static extern void uv_unref(UvHandle handle); + [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)] + public static extern int uv_fileno(UvHandle handle, ref IntPtr socket); + [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)] public static extern void uv_close(IntPtr handle, uv_close_cb close_cb); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs index 42315796d5..d75ec82902 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel.Filter; using Microsoft.AspNetCore.Server.Kestrel.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Networking; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.Logging; using Xunit; @@ -118,6 +119,22 @@ namespace Microsoft.AspNetCore.Server.KestrelTests Console.WriteLine("Started"); var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + if (PlatformApis.IsWindows) + { + const int SIO_LOOPBACK_FAST_PATH = (-1744830448); + var optionInValue = BitConverter.GetBytes(1); + try + { + socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); + } + catch + { + // If the operating system version on this machine did + // not support SIO_LOOPBACK_FAST_PATH (i.e. version + // prior to Windows 8 / Windows Server 2012), handle the exception + } + } + socket.NoDelay = true; socket.Connect(new IPEndPoint(IPAddress.Loopback, port)); socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n\r\nHello World")); socket.Shutdown(SocketShutdown.Send); @@ -475,6 +492,22 @@ namespace Microsoft.AspNetCore.Server.KestrelTests using (var server = new TestServer(App, testContext)) { var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + if (PlatformApis.IsWindows) + { + const int SIO_LOOPBACK_FAST_PATH = (-1744830448); + var optionInValue = BitConverter.GetBytes(1); + try + { + socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); + } + catch + { + // If the operating system version on this machine did + // not support SIO_LOOPBACK_FAST_PATH (i.e. version + // prior to Windows 8 / Windows Server 2012), handle the exception + } + } + socket.NoDelay = true; socket.Connect(IPAddress.Loopback, server.Port); await Task.Delay(200); socket.Dispose(); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/MultipleLoopTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/MultipleLoopTests.cs index 6c4d2e8ed0..16ebbd3421 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/MultipleLoopTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/MultipleLoopTests.cs @@ -235,6 +235,22 @@ namespace Microsoft.AspNetCore.Server.KestrelTests serverConnectionPipeAcceptedEvent.WaitOne(); var socket = new Socket(SocketType.Stream, ProtocolType.IP); + if (PlatformApis.IsWindows) + { + const int SIO_LOOPBACK_FAST_PATH = (-1744830448); + var optionInValue = BitConverter.GetBytes(1); + try + { + socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); + } + catch + { + // If the operating system version on this machine did + // not support SIO_LOOPBACK_FAST_PATH (i.e. version + // prior to Windows 8 / Windows Server 2012), handle the exception + } + } + socket.NoDelay = true; socket.Connect(IPAddress.Loopback, 54321); socket.Send(new byte[] { 6, 7, 8, 9 }); socket.Shutdown(SocketShutdown.Send); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/NetworkingTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/NetworkingTests.cs index 642d4c7729..c8824c70c7 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/NetworkingTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/NetworkingTests.cs @@ -93,6 +93,22 @@ namespace Microsoft.AspNetCore.Server.KestrelTests AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + if (PlatformApis.IsWindows) + { + const int SIO_LOOPBACK_FAST_PATH = (-1744830448); + var optionInValue = BitConverter.GetBytes(1); + try + { + socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); + } + catch + { + // If the operating system version on this machine did + // not support SIO_LOOPBACK_FAST_PATH (i.e. version + // prior to Windows 8 / Windows Server 2012), handle the exception + } + } + socket.NoDelay = true; #if DNX451 await Task.Factory.FromAsync( socket.BeginConnect, @@ -147,6 +163,22 @@ namespace Microsoft.AspNetCore.Server.KestrelTests AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + if (PlatformApis.IsWindows) + { + const int SIO_LOOPBACK_FAST_PATH = (-1744830448); + var optionInValue = BitConverter.GetBytes(1); + try + { + socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); + } + catch + { + // If the operating system version on this machine did + // not support SIO_LOOPBACK_FAST_PATH (i.e. version + // prior to Windows 8 / Windows Server 2012), handle the exception + } + } + socket.NoDelay = true; #if DNX451 await Task.Factory.FromAsync( socket.BeginConnect, @@ -234,6 +266,22 @@ namespace Microsoft.AspNetCore.Server.KestrelTests AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + if (PlatformApis.IsWindows) + { + const int SIO_LOOPBACK_FAST_PATH = (-1744830448); + var optionInValue = BitConverter.GetBytes(1); + try + { + socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); + } + catch + { + // If the operating system version on this machine did + // not support SIO_LOOPBACK_FAST_PATH (i.e. version + // prior to Windows 8 / Windows Server 2012), handle the exception + } + } + socket.NoDelay = true; #if DNX451 await Task.Factory.FromAsync( socket.BeginConnect, diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs index 5b6e857fd0..8e2ac64f69 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs @@ -8,6 +8,7 @@ using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel.Networking; using Xunit; namespace Microsoft.AspNetCore.Server.KestrelTests @@ -29,6 +30,22 @@ namespace Microsoft.AspNetCore.Server.KestrelTests public void Create(int port) { _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + if (PlatformApis.IsWindows) + { + const int SIO_LOOPBACK_FAST_PATH = (-1744830448); + var optionInValue = BitConverter.GetBytes(1); + try + { + _socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); + } + catch + { + // If the operating system version on this machine did + // not support SIO_LOOPBACK_FAST_PATH (i.e. version + // prior to Windows 8 / Windows Server 2012), handle the exception + } + } + _socket.NoDelay = true; _socket.Connect(new IPEndPoint(IPAddress.Loopback, port)); _stream = new NetworkStream(_socket, false);