From b5d70a0b745885fab8bec7a86b24db20d78d6cb3 Mon Sep 17 00:00:00 2001 From: SAI KISHAN A Date: Wed, 15 Jul 2026 12:57:43 +0530 Subject: [PATCH 1/2] Fix #840: Use SO_REUSEADDR in port probe to ignore TIME_WAIT On restart, sockets can remain in TIME_WAIT, preventing serve_cmd._is_port_free() from recognizing the port as available. This sets SO_REUSEADDR on POSIX systems so the probe matches uvicorn and Next.js behavior and correctly reclaims the preferred ports. --- .../src/repowise/cli/commands/serve_cmd.py | 2 ++ tests/unit/cli/test_serve_port_fallback.py | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/packages/cli/src/repowise/cli/commands/serve_cmd.py b/packages/cli/src/repowise/cli/commands/serve_cmd.py index 786a0a7c8..687c8422f 100644 --- a/packages/cli/src/repowise/cli/commands/serve_cmd.py +++ b/packages/cli/src/repowise/cli/commands/serve_cmd.py @@ -253,6 +253,8 @@ def _load_local_provider_config() -> None: def _is_port_free(host: str, port: int) -> bool: """Return True if *port* can be bound on *host* right now.""" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + if os.name != "nt": + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: sock.bind((host, port)) except OSError: diff --git a/tests/unit/cli/test_serve_port_fallback.py b/tests/unit/cli/test_serve_port_fallback.py index a5ecbec95..d3086a72d 100644 --- a/tests/unit/cli/test_serve_port_fallback.py +++ b/tests/unit/cli/test_serve_port_fallback.py @@ -68,3 +68,28 @@ def test_find_free_port_handles_contiguous_busy_block( chosen = serve_cmd._find_free_port("127.0.0.1", 3000, "web UI", max_attempts=5) assert isinstance(chosen, int) assert 1024 <= chosen <= 65535 + + +def test_is_port_free_ignores_time_wait() -> None: + """Regression test for issue #840: TIME_WAIT sockets shouldn't block the probe.""" + import os + + host = "127.0.0.1" + lis = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if os.name != "nt": + lis.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + lis.bind((host, 0)) + port = lis.getsockname()[1] + lis.listen(1) + + cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + cli.connect((host, port)) + conn, _ = lis.accept() + + # Active close from the server side pushes the listener's port into TIME_WAIT + lis.close() + conn.close() + cli.close() + + # The probe should report it as free because it uses SO_REUSEADDR too + assert serve_cmd._is_port_free(host, port) is True From 3ee26dfa41efa2acb982772316d5149ab5a17742 Mon Sep 17 00:00:00 2001 From: SAI KISHAN A Date: Thu, 16 Jul 2026 23:55:36 +0530 Subject: [PATCH 2/2] test: skip TIME_WAIT port probe test on Windows --- tests/unit/cli/test_serve_port_fallback.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/cli/test_serve_port_fallback.py b/tests/unit/cli/test_serve_port_fallback.py index d3086a72d..bbf43b019 100644 --- a/tests/unit/cli/test_serve_port_fallback.py +++ b/tests/unit/cli/test_serve_port_fallback.py @@ -7,6 +7,7 @@ from __future__ import annotations +import os import socket import pytest @@ -70,10 +71,9 @@ def test_find_free_port_handles_contiguous_busy_block( assert 1024 <= chosen <= 65535 +@pytest.mark.skipif(os.name == "nt", reason="SO_REUSEADDR probe fix is POSIX-only (issue #840)") def test_is_port_free_ignores_time_wait() -> None: """Regression test for issue #840: TIME_WAIT sockets shouldn't block the probe.""" - import os - host = "127.0.0.1" lis = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if os.name != "nt":