From dbd22db8ae9e404b07352159b4358ac13a1606bf Mon Sep 17 00:00:00 2001 From: Peyton Lee Date: Mon, 13 Oct 2025 15:38:02 -0700 Subject: [PATCH 1/4] feat: Automatically assign ports when available --- colorizer_data/bin/tfe_open.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/colorizer_data/bin/tfe_open.py b/colorizer_data/bin/tfe_open.py index 1f7f5da..cf21af4 100644 --- a/colorizer_data/bin/tfe_open.py +++ b/colorizer_data/bin/tfe_open.py @@ -5,6 +5,7 @@ import argparse import os import signal +import socket import webbrowser # 6465 and 6470 are unassigned ports according to @@ -13,6 +14,27 @@ default_directory_port = 6470 +def get_available_port(default_port): + try: + # Try binding to the default port + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("localhost", default_port)) + return default_port + except OSError: + # If the default port is in use, find an available port + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("localhost", 0)) # Bind to an available port + return s.getsockname()[1] # Return the dynamically assigned port + + +# Use the default ports or dynamically assign available ones +default_tfe_port = get_available_port(default_tfe_port) +default_directory_port = get_available_port(default_directory_port) + +print(f"TFE will run on port {default_tfe_port}") +print(f"Directory server will run on port {default_directory_port}") + + # Adapted from https://stackoverflow.com/a/21957017. class CORSRequestHandler(SimpleHTTPRequestHandler): def end_headers(self): From 5bce6f584cb086aaeada8b0b063f8f5c10f0894b Mon Sep 17 00:00:00 2001 From: Peyton Lee Date: Mon, 13 Oct 2025 15:53:02 -0700 Subject: [PATCH 2/4] fix: Prevent multiple port assignment --- colorizer_data/bin/tfe_open.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/colorizer_data/bin/tfe_open.py b/colorizer_data/bin/tfe_open.py index cf21af4..077990e 100644 --- a/colorizer_data/bin/tfe_open.py +++ b/colorizer_data/bin/tfe_open.py @@ -6,6 +6,7 @@ import os import signal import socket +from time import sleep import webbrowser # 6465 and 6470 are unassigned ports according to @@ -27,14 +28,6 @@ def get_available_port(default_port): return s.getsockname()[1] # Return the dynamically assigned port -# Use the default ports or dynamically assign available ones -default_tfe_port = get_available_port(default_tfe_port) -default_directory_port = get_available_port(default_directory_port) - -print(f"TFE will run on port {default_tfe_port}") -print(f"Directory server will run on port {default_directory_port}") - - # Adapted from https://stackoverflow.com/a/21957017. class CORSRequestHandler(SimpleHTTPRequestHandler): def end_headers(self): @@ -135,8 +128,16 @@ def main(): args = parser.parse_args() dataset_path = os.path.abspath(args.dataset_path) - tfe_port = args.tfe_port - directory_port = args.port + + tfe_port = get_available_port(args.tfe_port) + directory_port = get_available_port(args.port) + + if tfe_port != args.tfe_port: + print(f"Port {args.tfe_port} is in use. Using port {tfe_port} for TFE instead.") + if directory_port != args.port: + print( + f"Port {args.port} is in use. Using port {directory_port} for the directory server instead." + ) # Change working directory to the provided dataset directory new_cwd = os.getcwd() @@ -170,6 +171,7 @@ def main(): tfe_process = Process(target=serve_tfe, args=(tfe_port,)) tfe_process.start() + sleep(1) # Prevents a bug where the page fails to load print("Opening TFE at", url) webbrowser.open(url) From 179281479c95421737bc43261e8d984451fd5e5f Mon Sep 17 00:00:00 2001 From: Peyton Lee Date: Thu, 23 Oct 2025 15:24:28 -0700 Subject: [PATCH 3/4] fix: Changed `get_available_ports` to prevent collisions --- colorizer_data/bin/tfe_open.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/colorizer_data/bin/tfe_open.py b/colorizer_data/bin/tfe_open.py index 077990e..731d73a 100644 --- a/colorizer_data/bin/tfe_open.py +++ b/colorizer_data/bin/tfe_open.py @@ -7,6 +7,7 @@ import signal import socket from time import sleep +from typing import List import webbrowser # 6465 and 6470 are unassigned ports according to @@ -15,17 +16,28 @@ default_directory_port = 6470 -def get_available_port(default_port): +def get_available_ports(default_ports: List[int]) -> List[int]: + """ + Returns a list of available port numbers, attempting to use the provided + list of defaults. If a default port is in use, returns another available + port instead. + """ + # Recursive function. Calls itself while still inside the socket `with` + # block to prevent the same port from being allocated multiple times after + # being released. + if len(default_ports) == 0: + return [] + default_port = default_ports[0] try: # Try binding to the default port with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind(("localhost", default_port)) - return default_port + return [default_port] + get_available_ports(default_ports[1:]) except OSError: # If the default port is in use, find an available port with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind(("localhost", 0)) # Bind to an available port - return s.getsockname()[1] # Return the dynamically assigned port + return [s.getsockname()[1]] + get_available_ports(default_ports[1:]) # Adapted from https://stackoverflow.com/a/21957017. @@ -129,8 +141,7 @@ def main(): args = parser.parse_args() dataset_path = os.path.abspath(args.dataset_path) - tfe_port = get_available_port(args.tfe_port) - directory_port = get_available_port(args.port) + tfe_port, directory_port = get_available_ports([args.tfe_port, args.port]) if tfe_port != args.tfe_port: print(f"Port {args.tfe_port} is in use. Using port {tfe_port} for TFE instead.") From f9de813e5f93234665a3c23ca4a470fc037462a6 Mon Sep 17 00:00:00 2001 From: Peyton Lee Date: Thu, 23 Oct 2025 16:46:34 -0700 Subject: [PATCH 4/4] refactor: Occupy ports until used, remove recursion --- colorizer_data/bin/tfe_open.py | 53 ++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/colorizer_data/bin/tfe_open.py b/colorizer_data/bin/tfe_open.py index 731d73a..89ed792 100644 --- a/colorizer_data/bin/tfe_open.py +++ b/colorizer_data/bin/tfe_open.py @@ -7,7 +7,7 @@ import signal import socket from time import sleep -from typing import List +from typing import List, Tuple import webbrowser # 6465 and 6470 are unassigned ports according to @@ -16,28 +16,32 @@ default_directory_port = 6470 -def get_available_ports(default_ports: List[int]) -> List[int]: +def acquire_ports(default_ports: List[int]) -> List[Tuple[int, socket.socket]]: """ - Returns a list of available port numbers, attempting to use the provided - list of defaults. If a default port is in use, returns another available - port instead. + Returns a list of tuples containing reserved port numbers and open sockets + on those ports, attempting to use the provided list of defaults. If a + default port is in use, returns another available port instead. The open + sockets can be used to prevent other processes from acquiring the same + ports. + + Note that the returned sockets must be closed by the caller when no longer + needed. """ - # Recursive function. Calls itself while still inside the socket `with` - # block to prevent the same port from being allocated multiple times after - # being released. - if len(default_ports) == 0: - return [] - default_port = default_ports[0] - try: - # Try binding to the default port - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + ports = [] + for default_port in default_ports: + if not (0 <= default_port <= 65535): + raise ValueError("Port numbers must be between 0 and 65535.") + try: + # Try binding to the default port + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", default_port)) - return [default_port] + get_available_ports(default_ports[1:]) - except OSError: - # If the default port is in use, find an available port - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + ports.append((default_port, s)) + except OSError: + # If the default port is in use, find an available port + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 0)) # Bind to an available port - return [s.getsockname()[1]] + get_available_ports(default_ports[1:]) + ports.append((s.getsockname()[1], s)) + return ports # Adapted from https://stackoverflow.com/a/21957017. @@ -141,7 +145,9 @@ def main(): args = parser.parse_args() dataset_path = os.path.abspath(args.dataset_path) - tfe_port, directory_port = get_available_ports([args.tfe_port, args.port]) + (tfe_port, tfe_reserved_socket), (directory_port, directory_reserved_socket) = ( + acquire_ports([args.tfe_port, args.port]) + ) if tfe_port != args.tfe_port: print(f"Port {args.tfe_port} is in use. Using port {tfe_port} for TFE instead.") @@ -179,14 +185,19 @@ def main(): ) ) + # Start TFE server in a separate process + tfe_reserved_socket.close() tfe_process = Process(target=serve_tfe, args=(tfe_port,)) tfe_process.start() - sleep(1) # Prevents a bug where the page fails to load + # Prevents a bug where the page fails to load because the TFE server hasn't + # been initialized yet. + sleep(1) print("Opening TFE at", url) webbrowser.open(url) # Blocks until the server kills the process. + directory_reserved_socket.close() serve_directory(directory_port) # Exit gracefully