From 0ea20cda1f8d31413aeaa8772b96d150d72bfa8e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 13:33:39 -0700 Subject: [PATCH 01/13] add message to why port is skipped --- reflex/utils/processes.py | 54 +++++++++++++++------------------------ 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 703838f7965..15c0b4615a1 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -69,11 +69,8 @@ def _can_bind_at_port( try: with closing(socket.socket(address_family, socket.SOCK_STREAM)) as sock: sock.bind((address, port)) - except OverflowError: - return False - except PermissionError: - return False - except OSError: + except (OverflowError, PermissionError, OSError) as e: + console.warn(f"Unable to bind to {address}:{port} due to: {e}.") return False return True @@ -94,31 +91,7 @@ def is_process_on_port(port: int) -> bool: ) # Test IPv6 localhost (::1) -def change_port(port: int, _type: str) -> int: - """Change the port. - - Args: - port: The port. - _type: The type of the port. - - Returns: - The new port. - - Raises: - Exit: If the port is invalid or if the new port is occupied. - """ - new_port = port + 1 - if new_port < 0 or new_port > 65535: - console.error( - f"The {_type} port: {port} is invalid. It must be between 0 and 65535." - ) - raise click.exceptions.Exit(1) - if is_process_on_port(new_port): - return change_port(new_port, _type) - console.info( - f"The {_type} will run on port [bold underline]{new_port}[/bold underline]." - ) - return new_port +MAXIMUM_PORT = 2**16 - 1 def handle_port(service_name: str, port: int, auto_increment: bool) -> int: @@ -137,13 +110,28 @@ def handle_port(service_name: str, port: int, auto_increment: bool) -> int: Exit:when the port is in use. """ console.debug(f"Checking if {service_name.capitalize()} port: {port} is in use.") + if not is_process_on_port(port): console.debug(f"{service_name.capitalize()} port: {port} is not in use.") return port + if auto_increment: - return change_port(port, service_name) - console.error(f"{service_name.capitalize()} port: {port} is already in use.") - raise click.exceptions.Exit + for new_port in range(port + 1, MAXIMUM_PORT + 1): + if not is_process_on_port(new_port): + console.info( + f"The {service_name} will run on port [bold underline]{new_port}[/bold underline]." + ) + return new_port + console.debug( + f"{service_name.capitalize()} port: {new_port} is already in use." + ) + + # If we reach here, it means we couldn't find an available port. + console.error(f"Unable to find an available port for {service_name}") + else: + console.error(f"{service_name.capitalize()} port: {port} is already in use.") + + raise click.exceptions.Exit(1) @overload From 17ab8c0dd037d38ee875ec770c6f6242e6ea7184 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 14:26:36 -0700 Subject: [PATCH 02/13] set SO_REUSEADDR to 1 --- reflex/utils/processes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 15c0b4615a1..7ca9b3a52ed 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -68,6 +68,7 @@ def _can_bind_at_port( """ try: with closing(socket.socket(address_family, socket.SOCK_STREAM)) as sock: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((address, port)) except (OverflowError, PermissionError, OSError) as e: console.warn(f"Unable to bind to {address}:{port} due to: {e}.") From a6dece8b8030b39e9d14aadadd2f08a4fc5cbaff Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 14:29:53 -0700 Subject: [PATCH 03/13] change tests --- tests/units/utils/test_processes.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/units/utils/test_processes.py b/tests/units/utils/test_processes.py index f6cd4ab28da..c47d4638428 100644 --- a/tests/units/utils/test_processes.py +++ b/tests/units/utils/test_processes.py @@ -116,21 +116,19 @@ def test_is_process_on_port_permission_error(): assert result is True -@pytest.mark.parametrize("should_listen", [True, False]) -def test_is_process_on_port_concurrent_access(should_listen): +def test_is_process_on_port_concurrent_access(): """Test is_process_on_port works correctly with concurrent access. Args: should_listen: Whether the server socket should call listen() or just bind(). """ - def create_server_and_test(port_holder, listen): + def create_server_and_test(port_holder): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("127.0.0.1", 0)) - if listen: - server.listen(1) + server.listen(1) port = server.getsockname()[1] port_holder[0] = port @@ -140,9 +138,7 @@ def create_server_and_test(port_holder, listen): server.close() port_holder = [None] - thread = threading.Thread( - target=create_server_and_test, args=(port_holder, should_listen) - ) + thread = threading.Thread(target=create_server_and_test, args=(port_holder)) thread.start() # Wait a bit for the server to start From 506bfe1d7f42e4da375e1deaf0a7eed60276112e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 14:42:21 -0700 Subject: [PATCH 04/13] windows is weird --- reflex/utils/processes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 7ca9b3a52ed..dca5a0fc2d3 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -8,6 +8,7 @@ import signal import socket import subprocess +import sys from collections.abc import Callable, Generator, Sequence from concurrent import futures from contextlib import closing @@ -69,6 +70,8 @@ def _can_bind_at_port( try: with closing(socket.socket(address_family, socket.SOCK_STREAM)) as sock: sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if sys.platform == "win32": + sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) sock.bind((address, port)) except (OverflowError, PermissionError, OSError) as e: console.warn(f"Unable to bind to {address}:{port} due to: {e}.") From f80e1b390e9f759d877f4f842021a13fe279a912 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 14:46:47 -0700 Subject: [PATCH 05/13] sock close --- tests/units/utils/test_processes.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/units/utils/test_processes.py b/tests/units/utils/test_processes.py index c47d4638428..25d738f922a 100644 --- a/tests/units/utils/test_processes.py +++ b/tests/units/utils/test_processes.py @@ -17,6 +17,7 @@ def test_is_process_on_port_free_port(): with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: sock.bind(("127.0.0.1", 0)) free_port = sock.getsockname()[1] + sock.close() # Port should be free after socket is closed assert not is_process_on_port(free_port) @@ -117,11 +118,7 @@ def test_is_process_on_port_permission_error(): def test_is_process_on_port_concurrent_access(): - """Test is_process_on_port works correctly with concurrent access. - - Args: - should_listen: Whether the server socket should call listen() or just bind(). - """ + """Test is_process_on_port works correctly with concurrent access.""" def create_server_and_test(port_holder): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) From 97c6885e6bb17f489b2c028ceeda2ee85e7e8bad Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 14:57:07 -0700 Subject: [PATCH 06/13] not both --- reflex/utils/processes.py | 12 +++++++++--- tests/units/utils/test_processes.py | 1 - 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index dca5a0fc2d3..714373fc6ac 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -69,9 +69,15 @@ def _can_bind_at_port( """ try: with closing(socket.socket(address_family, socket.SOCK_STREAM)) as sock: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - if sys.platform == "win32": - sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) + sock.setsockopt( + socket.SOL_SOCKET, + ( + socket.SO_EXCLUSIVEADDRUSE + if sys.platform == "win32" + else socket.SO_REUSEADDR + ), + 1, + ) sock.bind((address, port)) except (OverflowError, PermissionError, OSError) as e: console.warn(f"Unable to bind to {address}:{port} due to: {e}.") diff --git a/tests/units/utils/test_processes.py b/tests/units/utils/test_processes.py index 25d738f922a..45f1a03187f 100644 --- a/tests/units/utils/test_processes.py +++ b/tests/units/utils/test_processes.py @@ -17,7 +17,6 @@ def test_is_process_on_port_free_port(): with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: sock.bind(("127.0.0.1", 0)) free_port = sock.getsockname()[1] - sock.close() # Port should be free after socket is closed assert not is_process_on_port(free_port) From 85c622d708563395d9d700beece0484baf5a8f2b Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 15:02:48 -0700 Subject: [PATCH 07/13] use weird guy to not use list --- tests/units/utils/test_processes.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/units/utils/test_processes.py b/tests/units/utils/test_processes.py index 45f1a03187f..9c972cf7455 100644 --- a/tests/units/utils/test_processes.py +++ b/tests/units/utils/test_processes.py @@ -118,8 +118,10 @@ def test_is_process_on_port_permission_error(): def test_is_process_on_port_concurrent_access(): """Test is_process_on_port works correctly with concurrent access.""" + shared = None - def create_server_and_test(port_holder): + def create_server_and_test(): + nonlocal shared server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("127.0.0.1", 0)) @@ -127,27 +129,25 @@ def create_server_and_test(port_holder): server.listen(1) port = server.getsockname()[1] - port_holder[0] = port + shared = port # Small delay to ensure the test runs while server is active time.sleep(0.1) server.close() - port_holder = [None] - thread = threading.Thread(target=create_server_and_test, args=(port_holder)) + thread = threading.Thread(target=create_server_and_test) thread.start() # Wait a bit for the server to start time.sleep(0.05) - if port_holder[0] is not None: - # Port should be occupied while server is running (both bound-only and listening) - assert is_process_on_port(port_holder[0]) + assert shared is not None + + # Port should be occupied while server is running (both bound-only and listening) + assert is_process_on_port(shared) thread.join() - # After thread ends and server closes, port should be free - if port_holder[0] is not None: - # Give it a moment for the socket to be fully released - time.sleep(0.1) - assert not is_process_on_port(port_holder[0]) + # Give it a moment for the socket to be fully released + time.sleep(0.1) + assert not is_process_on_port(shared) From 151eac8a451665deca65e50d73abbbd2dfe6693e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 15:36:52 -0700 Subject: [PATCH 08/13] != windows maybe --- reflex/utils/processes.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 714373fc6ac..047cce77cea 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -69,15 +69,8 @@ def _can_bind_at_port( """ try: with closing(socket.socket(address_family, socket.SOCK_STREAM)) as sock: - sock.setsockopt( - socket.SOL_SOCKET, - ( - socket.SO_EXCLUSIVEADDRUSE - if sys.platform == "win32" - else socket.SO_REUSEADDR - ), - 1, - ) + if sys.platform != "win32": + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((address, port)) except (OverflowError, PermissionError, OSError) as e: console.warn(f"Unable to bind to {address}:{port} due to: {e}.") From 069c26ccb9762c30a6461bc7c2ac0bedb8788c25 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 16:08:57 -0700 Subject: [PATCH 09/13] test 0.0.0.0 for CI --- reflex/utils/processes.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 047cce77cea..6b512c68bf9 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -87,11 +87,17 @@ def is_process_on_port(port: int) -> bool: Returns: Whether a process is running on the given port. """ - return not _can_bind_at_port( # Test IPv4 localhost (127.0.0.1) - socket.AF_INET, "127.0.0.1", port - ) or not _can_bind_at_port( - socket.AF_INET6, "::1", port - ) # Test IPv6 localhost (::1) + return ( + not _can_bind_at_port( + socket.AF_INET, "127.0.0.1", port + ) # Test IPv4 localhost (127.0.0.1) + or not _can_bind_at_port( + socket.AF_INET, "0.0.0.0", port + ) # Test IPv4 local network (0.0.0.0) + or not _can_bind_at_port( + socket.AF_INET6, "::1", port + ) # Test IPv6 localhost (::1) + ) MAXIMUM_PORT = 2**16 - 1 From 054afd0aa3907fa683e302e76e8babb69ddc9413 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 16:15:31 -0700 Subject: [PATCH 10/13] empty string ig --- reflex/utils/processes.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 6b512c68bf9..84300028896 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -88,15 +88,8 @@ def is_process_on_port(port: int) -> bool: Whether a process is running on the given port. """ return ( - not _can_bind_at_port( - socket.AF_INET, "127.0.0.1", port - ) # Test IPv4 localhost (127.0.0.1) - or not _can_bind_at_port( - socket.AF_INET, "0.0.0.0", port - ) # Test IPv4 local network (0.0.0.0) - or not _can_bind_at_port( - socket.AF_INET6, "::1", port - ) # Test IPv6 localhost (::1) + not _can_bind_at_port(socket.AF_INET, "", port) # Test IPv4 local network + or not _can_bind_at_port(socket.AF_INET6, "::", port) # Test IPv6 local network ) From 8c30c69bbb11561d3560e73c8edea56473a4ea0c Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 16:34:21 -0700 Subject: [PATCH 11/13] remove :: --- reflex/utils/processes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 84300028896..683474cd64d 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -89,7 +89,7 @@ def is_process_on_port(port: int) -> bool: """ return ( not _can_bind_at_port(socket.AF_INET, "", port) # Test IPv4 local network - or not _can_bind_at_port(socket.AF_INET6, "::", port) # Test IPv6 local network + or not _can_bind_at_port(socket.AF_INET6, "", port) # Test IPv6 local network ) From 87f9419207097a818c4d31d0707180e17e78993b Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 16:52:00 -0700 Subject: [PATCH 12/13] maybe --- tests/units/utils/test_processes.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/units/utils/test_processes.py b/tests/units/utils/test_processes.py index 9c972cf7455..7004da8295d 100644 --- a/tests/units/utils/test_processes.py +++ b/tests/units/utils/test_processes.py @@ -26,7 +26,6 @@ def test_is_process_on_port_occupied_port(): """Test is_process_on_port returns True when port is occupied.""" # Create a server socket to occupy a port server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(("127.0.0.1", 0)) server_socket.listen(1) @@ -44,7 +43,6 @@ def test_is_process_on_port_ipv6(): # Test with IPv6 socket try: server_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(("::1", 0)) server_socket.listen(1) @@ -64,7 +62,6 @@ def test_is_process_on_port_both_protocols(): """Test is_process_on_port detects occupation on either IPv4 or IPv6.""" # Create IPv4 server ipv4_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - ipv4_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ipv4_socket.bind(("127.0.0.1", 0)) ipv4_socket.listen(1) @@ -123,7 +120,6 @@ def test_is_process_on_port_concurrent_access(): def create_server_and_test(): nonlocal shared server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("127.0.0.1", 0)) server.listen(1) From f29c8719903987616322ef98eb43f72774509093 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Jul 2025 17:00:47 -0700 Subject: [PATCH 13/13] maybe --- tests/units/utils/test_processes.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/units/utils/test_processes.py b/tests/units/utils/test_processes.py index 7004da8295d..e767847cbff 100644 --- a/tests/units/utils/test_processes.py +++ b/tests/units/utils/test_processes.py @@ -15,7 +15,7 @@ def test_is_process_on_port_free_port(): """Test is_process_on_port returns False when port is free.""" # Find a free port with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: - sock.bind(("127.0.0.1", 0)) + sock.bind(("", 0)) free_port = sock.getsockname()[1] # Port should be free after socket is closed @@ -26,7 +26,7 @@ def test_is_process_on_port_occupied_port(): """Test is_process_on_port returns True when port is occupied.""" # Create a server socket to occupy a port server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.bind(("127.0.0.1", 0)) + server_socket.bind(("", 0)) server_socket.listen(1) occupied_port = server_socket.getsockname()[1] @@ -43,7 +43,7 @@ def test_is_process_on_port_ipv6(): # Test with IPv6 socket try: server_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) - server_socket.bind(("::1", 0)) + server_socket.bind(("", 0)) server_socket.listen(1) occupied_port = server_socket.getsockname()[1] @@ -62,7 +62,7 @@ def test_is_process_on_port_both_protocols(): """Test is_process_on_port detects occupation on either IPv4 or IPv6.""" # Create IPv4 server ipv4_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - ipv4_socket.bind(("127.0.0.1", 0)) + ipv4_socket.bind(("", 0)) ipv4_socket.listen(1) port = ipv4_socket.getsockname()[1] @@ -120,7 +120,7 @@ def test_is_process_on_port_concurrent_access(): def create_server_and_test(): nonlocal shared server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server.bind(("127.0.0.1", 0)) + server.bind(("", 0)) server.listen(1)