diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-09 23:00:59 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-09 23:00:59 +0000 |
| commit | be1f21aa1ec9d8666f96005ee203413441e6d220 (patch) | |
| tree | 636c85326cec99a9eb93c2798b106ff47f82b3c9 /tests/common/git_server.rs | |
| parent | 615bfa0e3e892a22f1691a6a1172ea755a7c3149 (diff) | |
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.
Diffstat (limited to 'tests/common/git_server.rs')
| -rw-r--r-- | tests/common/git_server.rs | 33 |
1 files changed, 14 insertions, 19 deletions
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 { | |||
| 128 | ); | 128 | ); |
| 129 | } | 129 | } |
| 130 | 130 | ||
| 131 | // 4. Find a free port | 131 | // 4. Create and bind listener (eliminates port race condition) |
| 132 | let port = find_free_port(); | 132 | let std_listener = |
| 133 | let addr: SocketAddr = ([127, 0, 0, 1], port).into(); | 133 | std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind to random port"); |
| 134 | let port = std_listener | ||
| 135 | .local_addr() | ||
| 136 | .expect("Failed to get local addr") | ||
| 137 | .port(); | ||
| 138 | |||
| 139 | // Convert to tokio listener (keeps port bound) | ||
| 140 | std_listener | ||
| 141 | .set_nonblocking(true) | ||
| 142 | .expect("Failed to set non-blocking"); | ||
| 143 | let listener = | ||
| 144 | TcpListener::from_std(std_listener).expect("Failed to convert to tokio listener"); | ||
| 134 | 145 | ||
| 135 | // 5. Create shutdown channel | 146 | // 5. Create shutdown channel |
| 136 | let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>(); | 147 | let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>(); |
| 137 | 148 | ||
| 138 | // 6. Start the HTTP server | 149 | // 6. Start the HTTP server |
| 139 | let repo_path = Arc::new(bare_repo_path); | 150 | let repo_path = Arc::new(bare_repo_path); |
| 140 | let listener = TcpListener::bind(addr) | ||
| 141 | .await | ||
| 142 | .expect("Failed to bind to address"); | ||
| 143 | 151 | ||
| 144 | let handle = tokio::spawn(async move { | 152 | let handle = tokio::spawn(async move { |
| 145 | println!("[SmartGitServer] Server loop started on port {}", port); | 153 | println!("[SmartGitServer] Server loop started on port {}", port); |
| @@ -296,19 +304,6 @@ fn guess_content_type(path: &Path) -> &'static str { | |||
| 296 | } | 304 | } |
| 297 | } | 305 | } |
| 298 | 306 | ||
| 299 | /// Find a free port to use for the server. | ||
| 300 | fn find_free_port() -> u16 { | ||
| 301 | use std::net::TcpListener; | ||
| 302 | |||
| 303 | let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind to random port"); | ||
| 304 | let port = listener | ||
| 305 | .local_addr() | ||
| 306 | .expect("Failed to get local addr") | ||
| 307 | .port(); | ||
| 308 | drop(listener); | ||
| 309 | port | ||
| 310 | } | ||
| 311 | |||
| 312 | /// Wait for the server to be ready to accept connections. | 307 | /// Wait for the server to be ready to accept connections. |
| 313 | async fn wait_for_server_ready(port: u16) { | 308 | async fn wait_for_server_ready(port: u16) { |
| 314 | let max_attempts = 50; // 5 seconds total | 309 | let max_attempts = 50; // 5 seconds total |