There is a potential race condition in the current implementation of the free_port fixture, which is used in both server_runtime.rs and server_runtime_more.rs. The fixture currently binds to a free port and immediately releases it, which can allow another process to claim the port before the test or server setup code does. This can lead to flaky tests or unexpected failures.
Suggested Solution:
- Replace the
free_port fixture with a free_listener fixture that returns a bound TcpListener. This keeps the port reserved until the test or server is ready to use it, preventing other processes from claiming the port in the meantime.
- Update all tests and helpers that use
free_port() to use free_listener() instead. If only the address is needed, extract it with listener.local_addr().unwrap().
- Where possible, pass the
TcpListener directly to the server setup code instead of just the address.
Example Implementation:
#[fixture]
/// Returns a bound TcpListener on a free port for use in tests.
/// This prevents race conditions by keeping the port reserved until the test is ready.
pub fn free_listener() -> std::net::TcpListener {
let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0);
std::net::TcpListener::bind(addr).expect("Failed to bind to localhost:0")
}
Action Items:
- Implement the
free_listener fixture as shown above.
- Refactor all usages of
free_port in the codebase to use free_listener.
- Update server setup code to accept a
TcpListener where possible.
- Remove the old
free_port fixture once all usages have been migrated.
Let us know if you need further guidance or code examples for the migration!
Issue: #258
I created this issue for @leynos from #244 (comment).
Tips and commands
Getting Help
There is a potential race condition in the current implementation of the
free_portfixture, which is used in bothserver_runtime.rsandserver_runtime_more.rs. The fixture currently binds to a free port and immediately releases it, which can allow another process to claim the port before the test or server setup code does. This can lead to flaky tests or unexpected failures.Suggested Solution:
free_portfixture with afree_listenerfixture that returns a boundTcpListener. This keeps the port reserved until the test or server is ready to use it, preventing other processes from claiming the port in the meantime.free_port()to usefree_listener()instead. If only the address is needed, extract it withlistener.local_addr().unwrap().TcpListenerdirectly to the server setup code instead of just the address.Example Implementation:
Action Items:
free_listenerfixture as shown above.free_portin the codebase to usefree_listener.TcpListenerwhere possible.free_portfixture once all usages have been migrated.Let us know if you need further guidance or code examples for the migration!
Issue: #258
I created this issue for @leynos from #244 (comment).
Tips and commands
Getting Help