Skip to content

[Repo Assist] test: use TcpListener(0) for reliable free-port selection in HTTP test server#1724

Merged
dsyme merged 2 commits intomainfrom
repo-assist/eng-fix-windows-ci-port-binding-2026-04-03-003461bcee6010de
Apr 3, 2026
Merged

[Repo Assist] test: use TcpListener(0) for reliable free-port selection in HTTP test server#1724
dsyme merged 2 commits intomainfrom
repo-assist/eng-fix-windows-ci-port-binding-2026-04-03-003461bcee6010de

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot commented Apr 3, 2026

🤖 This is an automated pull request from Repo Assist, an AI assistant for this repository.

Problem

The startHttpLocalServer() helper in Http.fs selected a free port by:

  1. Picking a random number from 10000–65000
  2. Checking IPGlobalProperties.GetActiveTcpListeners() in a loop until no conflict

This approach has a TOCTOU race (time-of-check to time-of-use): another process can grab the port between the check and Kestrel's bind. On busy Windows CI agents (many parallel processes, ephemeral ports recycled rapidly) this caused intermittent failures1 Failed, 2889 Passed — visible in the Windows CI job for PR #1717.

The testMultipartFormDataBodySize tests already carried a Windows-CI-specific Assert.Ignore workaround for this exact issue.

Fix

Replace the random-port-then-check logic with TcpListener(IPAddress.Loopback, 0):

let freePort =
    let listener = new TcpListener(System.Net.IPAddress.Loopback, 0)
    listener.Start()
    let port = (listener.LocalEndpoint :?> System.Net.IPEndPoint).Port
    listener.Stop()
    port

The OS assigns a free port (via the kernel, atomically). The window between Stop() and Kestrel's Bind() is microseconds — negligible in practice and far better than the previous approach.

Also removes the now-unnecessary Windows-CI skip workaround from testMultipartFormDataBodySize, and fixes Dispose() to properly await StopAsync instead of fire-and-forget.

Test Status

  • dotnet test tests/FSharp.Data.Core.Tests/ --filter Http136 passed, 0 failed, 0 skipped (multipart tests now run on all platforms without the skip workaround)
  • dotnet test tests/FSharp.Data.Core.Tests/2896 passed, 0 failed

Generated by Repo Assist

Generated by 🌈 Repo Assist, see workflow run. Learn more.

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@4ea8c81959909f40373e2a5c2b7fdb54ea19e0a5

…t server

Replace the random-port-plus-check approach with TcpListener(0), which
asks the OS to assign a free port. The TOCTOU window (between TcpListener.Stop()
and Kestrel's bind) is microseconds, far more reliable than the previous
approach that randomly selected a port from 10000-65000 and then checked
IPGlobalProperties.GetActiveTcpListeners().

Also removes the Windows-CI skip workaround from testMultipartFormDataBodySize,
since the root cause (flaky port selection) is now fixed.

Also fixes Dispose() to properly await StopAsync instead of fire-and-forget.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dsyme dsyme marked this pull request as ready for review April 3, 2026 14:45
@dsyme dsyme merged commit 77745ac into main Apr 3, 2026
2 checks passed
@dsyme dsyme deleted the repo-assist/eng-fix-windows-ci-port-binding-2026-04-03-003461bcee6010de branch April 3, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant