From c7832345500a2752e7b2e9f9096ad57e4d70ab62 Mon Sep 17 00:00:00 2001 From: "datadog-official[bot]" <214633350+datadog-official[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 15:03:12 +0000 Subject: [PATCH] [Datadog Workflows] create/update file --- .../src/Demos/Samples.BuggyBits/Program.cs | 154 ++++++++++++------ 1 file changed, 102 insertions(+), 52 deletions(-) diff --git a/profiler/src/Demos/Samples.BuggyBits/Program.cs b/profiler/src/Demos/Samples.BuggyBits/Program.cs index 7898680849a3..ad7d24fbcf27 100644 --- a/profiler/src/Demos/Samples.BuggyBits/Program.cs +++ b/profiler/src/Demos/Samples.BuggyBits/Program.cs @@ -54,33 +54,15 @@ public static async Task Main(string[] args) ParseCommandLine(args, out _disableLogs, out var timeout, out var iterations, out var scenario, out var nbIdleThreads); + // Resolve the URL/port before building the host so that Kestrel is configured + // with a port that is actually free at bind time. This avoids a TOCTOU race: + // previously the host was built with the original --urls port, and the + // GetValidPort probe ran only after the build (too late to affect Kestrel). + args = ResolveListenUrl(args, out var rootUrl); + WriteLine($"Listening to {rootUrl}"); + using (var host = CreateHostBuilder(args).Build()) { - // ASP.NET Core accepts listening url via what is set by Visual Studio - // (from the launchsettings.json). It could be overriden by --Urls - // on the command line - var configuration = host.Services.GetService(typeof(IConfiguration)) as IConfiguration; - var rootUrl = configuration["urls"]; - - // otherwise, use the default ASP.NET Core value - if (string.IsNullOrEmpty(rootUrl)) - { - rootUrl = "http://localhost:5000"; - } - - // avoid race condition in CI to find an available port - int port = -1; - if (int.TryParse(rootUrl.Substring(rootUrl.LastIndexOf(':') + 1), out port)) - { - port = GetValidPort(port, 3); - if (port != -1) - { - rootUrl = rootUrl.Substring(0, rootUrl.LastIndexOf(':') + 1) + port; - } - } - - WriteLine($"Listening to {rootUrl}"); - var cts = new CancellationTokenSource(); using (var selfInvoker = new SelfInvoker(cts.Token, scenario, nbIdleThreads, _disableLogs)) { @@ -177,46 +159,114 @@ public static int GetOpenPort() } } - private static int GetValidPort(int initialPort, int retries) + /// + /// Resolves the Kestrel listen URL by finding a free port before the host is built. + /// This ensures receives the correct port in + /// so that Kestrel binds without a race. + /// + /// Original command-line args (may contain --urls). + /// The resolved URL with a free port substituted in. + /// Updated args array where --urls points to the resolved URL. + private static string[] ResolveListenUrl(string[] args, out string resolvedUrl) { - var port = initialPort; - bool isPortValid = false; - while (true) + // Extract the --urls value from command-line args (ASP.NET Core convention). + // Fall back to the default Kestrel URL if not specified. + string urlFromArgs = null; + for (int i = 0; i < args.Length - 1; i++) { - // seems like we can't reuse a listener if it fails to start, - // so create a new listener each time we retry - var listener = new HttpListener(); - listener.Prefixes.Add($"http://127.0.0.1:{port}/"); - listener.Prefixes.Add($"http://localhost:{port}/"); - - try + if (args[i].Equals("--urls", StringComparison.OrdinalIgnoreCase)) { - listener.Start(); - - // success - isPortValid = true; + urlFromArgs = args[i + 1]; break; } - catch (HttpListenerException) when (retries > 0) - { - // only catch the exception if there are retries left - port = GetOpenPort(); - retries--; - } - finally + } + + var baseUrl = urlFromArgs ?? "http://localhost:5000"; + + // Find a free port (up to 5 attempts with fresh ephemeral ports on each retry). + resolvedUrl = FindFreePortUrl(baseUrl, retries: 5); + + // Replace (or inject) --urls so CreateHostBuilder configures Kestrel correctly. + return ReplaceUrlInArgs(args, resolvedUrl); + } + + /// + /// Returns with its port component replaced by the first + /// available port found within attempts. + /// + private static string FindFreePortUrl(string baseUrl, int retries) + { + var lastColon = baseUrl.LastIndexOf(':'); + if (lastColon < 0 || !int.TryParse(baseUrl.Substring(lastColon + 1), out int initialPort)) + { + // No explicit port — return as-is and let Kestrel use its default. + return baseUrl; + } + + var urlPrefix = baseUrl.Substring(0, lastColon + 1); // e.g. "http://localhost:" + var port = initialPort; + + for (int attempt = 0; attempt <= retries; attempt++) + { + if (IsPortAvailable(port)) { - listener.Close(); + return urlPrefix + port; } + + // Port is busy — pick a fresh ephemeral port for the next attempt. + port = GetOpenPort(); } - if (isPortValid) + // All retries exhausted — use the last candidate and surface a real error if + // it is still busy (better than silently starting on the wrong port). + return urlPrefix + port; + } + + /// + /// Returns true if appears to be free on the loopback + /// interface; false if it is already in use. + /// + private static bool IsPortAvailable(int port) + { + var listener = new HttpListener(); + listener.Prefixes.Add($"http://127.0.0.1:{port}/"); + listener.Prefixes.Add($"http://localhost:{port}/"); + try { - return port; + listener.Start(); + return true; } - else + catch (HttpListenerException) { - return -1; // no valid port found + return false; } + finally + { + listener.Close(); + } + } + + /// + /// Returns a copy of where the value after --urls is + /// replaced with . Appends --urls newUrl if the flag + /// is not already present. + /// + private static string[] ReplaceUrlInArgs(string[] args, string newUrl) + { + var list = new System.Collections.Generic.List(args); + for (int i = 0; i < list.Count - 1; i++) + { + if (list[i].Equals("--urls", StringComparison.OrdinalIgnoreCase)) + { + list[i + 1] = newUrl; + return list.ToArray(); + } + } + + // "--urls" not found — append so Kestrel picks up the resolved port. + list.Add("--urls"); + list.Add(newUrl); + return list.ToArray(); } private static void ParseCommandLine(string[] args, out bool disableLogs, out TimeSpan timeout, out int iterations, out Scenario scenario, out int nbIdleThreads)