diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Program.cs b/src/BenchmarksApps/TechEmpower/Minimal/Program.cs index 287f7a035..05d3b7b8c 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Program.cs +++ b/src/BenchmarksApps/TechEmpower/Minimal/Program.cs @@ -5,6 +5,10 @@ using Minimal; using Minimal.Database; using Minimal.Models; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; +using System.Net.Sockets; +using System.Net; +using System.Runtime.InteropServices; var builder = WebApplication.CreateBuilder(args); @@ -13,9 +17,41 @@ builder.WebHost.ConfigureKestrel(options => { - options.AllowSynchronousIO = true; + options.AllowSynchronousIO = true; }); +// Allow multiple processes bind to the same port. This also "works" on Windows in that it will +// prevent address in use errors and hand off to another process if no others are available, +// but it wouldn't round-robin new connections between processes like it will on Linux. +if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) +{ + builder.WebHost.UseSockets(options => + { + options.CreateBoundListenSocket = endpoint => + { + if (endpoint is not IPEndPoint ip) + { + return SocketTransportOptions.CreateDefaultBoundListenSocket(endpoint); + } + + // Normally, we'd call CreateDefaultBoundListenSocket for the IPEndpoint too, but we need + // to set ReuseAddress before calling bind, and CreateDefaultBoundListenSocket calls bind. + var listenSocket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + + // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 + if (ip.Address.Equals(IPAddress.IPv6Any)) + { + listenSocket.DualMode = true; + } + + listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + listenSocket.Bind(ip); + + return listenSocket; + }; + }); +} + // Load custom configuration var appSettings = new AppSettings(); builder.Configuration.Bind(appSettings); @@ -40,7 +76,8 @@ var createFortunesTemplate = RazorSlice.ResolveSliceFactory>("/Templates/Fortunes.cshtml"); var htmlEncoder = CreateHtmlEncoder(); -app.MapGet("/fortunes", async (HttpContext context, Db db) => { +app.MapGet("/fortunes", async (HttpContext context, Db db) => +{ var fortunes = await db.LoadFortunesRows(); var template = (RazorSliceHttpResult>)createFortunesTemplate(fortunes); template.HtmlEncoder = htmlEncoder; diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs index 58238dbe2..b379841f5 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs @@ -2,10 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net; +using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; using Microsoft.Extensions.Configuration; #if DATABASE using Npgsql; @@ -106,6 +109,32 @@ public static IWebHost BuildWebHost(string[] args) if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { options.UnsafePreferInlineScheduling = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS") == "1"; + + // Allow multiple processes bind to the same port. This also "works" on Windows in that it will + // prevent address in use errors and hand off to another process if no others are available, + // but it wouldn't round-robin new connections between processes like it will on Linux. + options.CreateBoundListenSocket = endpoint => + { + if (endpoint is not IPEndPoint ip) + { + return SocketTransportOptions.CreateDefaultBoundListenSocket(endpoint); + } + + // Normally, we'd call CreateDefaultBoundListenSocket for the IPEndpoint too, but we need + // to set ReuseAddress before calling bind, and CreateDefaultBoundListenSocket calls bind. + var listenSocket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + + // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 + if (ip.Address.Equals(IPAddress.IPv6Any)) + { + listenSocket.DualMode = true; + } + + listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + listenSocket.Bind(ip); + + return listenSocket; + }; } });