From be1f21aa1ec9d8666f96005ee203413441e6d220 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 9 Jan 2026 23:00:59 +0000 Subject: fix: eliminate port binding race condition in SimpleGitServer SimpleGitServer had a TOCTOU race where find_free_port() would bind a port, immediately release it, then the caller would try to bind it - allowing another process to grab the port in between. This caused intermittent test failures. Changed to bind the port once and keep it bound while converting from std to tokio listener, matching the pattern already used in SmartGitServer. Deleted the now-unused find_free_port() helper function. --- tests/common/git_server.rs | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) (limited to 'tests/common/git_server.rs') diff --git a/tests/common/git_server.rs b/tests/common/git_server.rs index 9fb62df..3190901 100644 --- a/tests/common/git_server.rs +++ b/tests/common/git_server.rs @@ -128,18 +128,26 @@ impl SimpleGitServer { ); } - // 4. Find a free port - let port = find_free_port(); - let addr: SocketAddr = ([127, 0, 0, 1], port).into(); + // 4. Create and bind listener (eliminates port race condition) + let std_listener = + std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind to random port"); + let port = std_listener + .local_addr() + .expect("Failed to get local addr") + .port(); + + // Convert to tokio listener (keeps port bound) + std_listener + .set_nonblocking(true) + .expect("Failed to set non-blocking"); + let listener = + TcpListener::from_std(std_listener).expect("Failed to convert to tokio listener"); // 5. Create shutdown channel let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>(); // 6. Start the HTTP server let repo_path = Arc::new(bare_repo_path); - let listener = TcpListener::bind(addr) - .await - .expect("Failed to bind to address"); let handle = tokio::spawn(async move { println!("[SmartGitServer] Server loop started on port {}", port); @@ -296,19 +304,6 @@ fn guess_content_type(path: &Path) -> &'static str { } } -/// Find a free port to use for the server. -fn find_free_port() -> u16 { - use std::net::TcpListener; - - let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind to random port"); - let port = listener - .local_addr() - .expect("Failed to get local addr") - .port(); - drop(listener); - port -} - /// Wait for the server to be ready to accept connections. async fn wait_for_server_ready(port: u16) { let max_attempts = 50; // 5 seconds total -- cgit v1.2.3