Skip to content

The port reservation test can fail against a port it just released #461

Description

@thcp

The problem

a_free_port_is_granted_as_asked in desktop/src-tauri/src/main.rs fails intermittently in CI:

thread 'tests::a_free_port_is_granted_as_asked' panicked at src/main.rs:5231:9:
assertion `left == right` failed
  left: 62251
 right: 62250

The two numbers are always adjacent, which is the tell.

Why it happens

The test proves a port is available the only way it can: it binds one, reads the number, and lets go.

let probe = std::net::TcpListener::bind(("0.0.0.0", 0)).unwrap();
let wanted = probe.local_addr().unwrap().port();
drop(probe);

let (got, _guard) = super::reserve_port("0.0.0.0", wanted).unwrap();
assert_eq!(got, wanted);

Between drop(probe) and reserve_port, that number is free for anyone. Cargo runs the tests in this binary in parallel and several of them stand up throwaway TCP listeners on ephemeral ports. When one of them calls bind(0) inside that window, the OS hands it the number this test just released. claim_port then fails, reserve_port correctly falls back to free_port, and the next number up comes back.

So the failure is the test racing itself. reserve_port behaves exactly as designed in the failing run.

Who it affects

Anyone whose PR happens to lose the race. The suite is green on main today, but the window is real and it widens with every test in this binary that opens an ephemeral socket. It went from theoretical to observed when a third such test was added in #460, and a red check that means nothing is worse than no check: it trains people to re-run rather than read.

Constraints for a fix

  • The window cannot be closed by ordering alone. Something has to hold the port, and if the test holds it then reserve_port cannot claim it, which is the opposite assertion.
  • The assertion is worth keeping. "The configured port is honoured when it is genuinely free" is the half of reserve_port that the fallback tests do not cover.
  • Worth noting that an ephemeral port is not what reserve_port is given in production. It receives the user's configured port, 8000 by default. The test reaching for bind(0) is what puts it in contention with every other bind(0) in the binary.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions