@@ -40,30 +40,59 @@ def __init__(
4040 def _check_port_available (self , port : int ) -> bool :
4141 """Check if a specific port is available."""
4242 try :
43- with socket .socket (socket .AF_INET , socket .SOCK_STREAM ) as s :
44- s .bind (('localhost' , port ))
43+ # Create a socket
44+ s = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
45+ self .logger .debug (f"Created socket for port { port } check" )
46+
47+ # Set socket options
48+ s .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
49+ self .logger .debug ("Set SO_REUSEADDR" )
50+
51+ # Bind to the port
52+ try :
53+ s .bind (('127.0.0.1' , port ))
54+ self .logger .debug (f"Successfully bound to port { port } " )
55+ s .listen (1 )
56+ self .logger .debug (f"Successfully listening on port { port } " )
57+ s .close ()
58+ self .logger .debug (f"Port { port } is available" )
4559 return True
46- except OSError :
60+ except OSError as e :
61+ self .logger .debug (f"Failed to bind to port { port } : { str (e )} " )
62+ return False
63+ finally :
64+ try :
65+ s .close ()
66+ self .logger .debug ("Socket closed" )
67+ except :
68+ pass
69+
70+ except Exception as e :
71+ self .logger .debug (f"Unexpected error checking port { port } : { str (e )} " )
4772 return False
4873
4974 def _get_server_port (self ) -> int :
50- """Get and validate the server port.
75+ """Get and validate the server port."""
76+ from .exceptions import LumeConfigError
5177
52- Returns:
53- int: The validated port number
54-
55- Raises:
56- RuntimeError: If no port was specified
57- LumeConfigError: If the requested port is not available
58- """
5978 if self .requested_port is None :
60- raise RuntimeError ( "No port specified for lume server" )
79+ raise LumeConfigError ( "Port must be specified when starting a new server" )
6180
62- if not self ._check_port_available (self .requested_port ):
63- from .exceptions import LumeConfigError
64- raise LumeConfigError (f"Requested port { self .requested_port } is not available" )
81+ self .logger .debug (f"Checking availability of port { self .requested_port } " )
82+
83+ # Try multiple times with a small delay
84+ for attempt in range (3 ):
85+ if attempt > 0 :
86+ self .logger .debug (f"Retrying port check (attempt { attempt + 1 } )" )
87+ time .sleep (1 )
88+
89+ if self ._check_port_available (self .requested_port ):
90+ self .logger .debug (f"Port { self .requested_port } is available" )
91+ return self .requested_port
92+ else :
93+ self .logger .debug (f"Port { self .requested_port } check failed on attempt { attempt + 1 } " )
6594
66- return self .requested_port
95+ raise LumeConfigError ( f"Requested port { self .requested_port } is not available after 3 attempts" )
6796
6897 async def _ensure_server_running (self ) -> None :
6998 """Ensure the lume server is running, start it if it's not."""
@@ -228,46 +257,34 @@ async def _ensure_server_running(self) -> None:
228257 raise RuntimeError (f"Failed to start lume server: { str (e )} " )
229258
230259 async def _start_server (self ) -> None :
231- """Start the lume server using a managed shell script ."""
260+ """Start the lume server using the lume executable ."""
232261 self .logger .debug ("Starting PyLume server" )
262+
263+ # Get absolute path to lume executable in the same directory as this file
233264 lume_path = os .path .join (os .path .dirname (__file__ ), "lume" )
234265 if not os .path .exists (lume_path ):
235266 raise RuntimeError (f"Could not find lume binary at { lume_path } " )
236-
237- script_file = None
267+
238268 try :
269+ # Make executable
239270 os .chmod (lume_path , 0o755 )
271+
272+ # Get and validate port
240273 self .port = self ._get_server_port ()
241274 self .base_url = f"http://localhost:{ self .port } /lume"
242-
243- # Create shell script with trap for process management
244- script_content = f"""#!/bin/bash
245- trap 'kill $(jobs -p)' EXIT
246- exec { lume_path } serve --port { self .port }
247- """
248- script_dir = os .path .dirname (lume_path )
249- script_file = tempfile .NamedTemporaryFile (
250- mode = 'w' ,
251- suffix = '.sh' ,
252- dir = script_dir ,
253- delete = True
254- )
255- script_file .write (script_content )
256- script_file .flush ()
257- os .chmod (script_file .name , 0o755 )
258-
259- # Set up output handling - just use a temp file
275+
276+ # Set up output handling
260277 self .output_file = tempfile .NamedTemporaryFile (mode = 'w+' , delete = False )
261278
262- # Start the managed server process
279+ # Start the server process with the lume executable
263280 env = os .environ .copy ()
264- env ["RUST_BACKTRACE" ] = "1"
281+ env ["RUST_BACKTRACE" ] = "1" # Enable backtrace for better error reporting
265282
266283 self .server_process = subprocess .Popen (
267- ['/bin/bash' , script_file . name ],
284+ [lume_path , "serve" , "--port" , str ( self . port ) ],
268285 stdout = self .output_file ,
269286 stderr = subprocess .STDOUT ,
270- cwd = script_dir ,
287+ cwd = os . path . dirname ( lume_path ), # Run from same directory as executable
271288 env = env
272289 )
273290
@@ -278,13 +295,6 @@ async def _start_server(self) -> None:
278295 except Exception as e :
279296 await self ._cleanup ()
280297 raise RuntimeError (f"Failed to start lume server process: { str (e )} " )
281- finally :
282- # Ensure script file is cleaned up
283- if script_file :
284- try :
285- script_file .close ()
286- except :
287- pass
288298
289299 async def _tail_log (self ) -> None :
290300 """Read and display server log output in debug mode."""
0 commit comments