From 40ab5f92681199b75c614db4531e6d9034bca470 Mon Sep 17 00:00:00 2001
From: Stephen Halter <halter73@gmail.com>
Date: Wed, 16 Oct 2024 14:09:39 -0700
Subject: [PATCH 1/2] Add port sharing support to PlatformBenchmarks on Linux

---
 .../TechEmpower/PlatformBenchmarks/Program.cs | 29 +++++++++++++++++++
 1 file changed, 29 insertions(+)

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;
+                    };
                 }
             });
 

From 7c4a6edfc94be21bc28cf786880bc547bf18ef53 Mon Sep 17 00:00:00 2001
From: Stephen Halter <halter73@gmail.com>
Date: Wed, 16 Oct 2024 14:12:04 -0700
Subject: [PATCH 2/2] Add port sharing support to Minimal benchmarks on Linux

---
 .../TechEmpower/Minimal/Program.cs            | 41 ++++++++++++++++++-
 1 file changed, 39 insertions(+), 2 deletions(-)

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<List<Fortune>>("/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<List<Fortune>>)createFortunesTemplate(fortunes);
     template.HtmlEncoder = htmlEncoder;