diff --git a/core/inference_client.py b/core/inference_client.py index 318c9a7..6ecb09c 100644 --- a/core/inference_client.py +++ b/core/inference_client.py @@ -24,6 +24,8 @@ class InferenceClient: def __init__(self): self.server_process: Optional[subprocess.Popen] = None self._server_lock = threading.Lock() + self.log_file = None + self.log_file_path = None def start_server(self): """Start the inference server process.""" @@ -52,28 +54,54 @@ class InferenceClient: server_env[key] = value print(f"[FaceMask] Loaded environment from: {env_file}") - # Ensure PYTHONPATH includes project root - pythonpath = server_env.get('PYTHONPATH', '') - if pythonpath: - server_env['PYTHONPATH'] = f"{root_dir}:{pythonpath}" - else: - server_env['PYTHONPATH'] = root_dir + # Clean PYTHONPATH to avoid conflicts with Nix Python packages + # Only include project root to allow local imports + server_env['PYTHONPATH'] = root_dir + + # Remove Python-related environment variables that might cause conflicts + # These can cause venv to import packages from Nix instead of venv + env_vars_to_remove = [ + 'PYTHONUNBUFFERED', + '__PYVENV_LAUNCHER__', # macOS venv variable + 'VIRTUAL_ENV', # Will be set by venv's Python automatically + ] + for var in env_vars_to_remove: + server_env.pop(var, None) # If there's a venv in the project, add it to PATH venv_bin = os.path.join(root_dir, ".venv", "bin") if os.path.isdir(venv_bin): + # Build a clean PATH with venv first, then essential system paths + # Filter out any Nix Python-specific paths to avoid version conflicts current_path = server_env.get('PATH', '') - server_env['PATH'] = f"{venv_bin}:{current_path}" + path_entries = current_path.split(':') + + # Filter out Nix Python 3.11 paths + filtered_paths = [ + p for p in path_entries + if not ('/python3.11/' in p.lower() or '/python3-3.11' in p.lower()) + ] + + # Reconstruct PATH with venv first + clean_path = ':'.join([venv_bin] + filtered_paths) + server_env['PATH'] = clean_path print(f"[FaceMask] Using venv from: {venv_bin}") + # Prepare log file for server output + import tempfile + log_dir = tempfile.gettempdir() + self.log_file_path = os.path.join(log_dir, "facemask_server.log") + self.log_file = open(self.log_file_path, 'w', buffering=1) # Line buffered + print(f"[FaceMask] Server log: {self.log_file_path}") + # Start process with 'python' command (will use venv if PATH is set correctly) self.server_process = subprocess.Popen( - ["python", server_script], + ["python", "-u", server_script], # -u for unbuffered output cwd=root_dir, text=True, env=server_env, - stdout=subprocess.PIPE, # Capture stdout - stderr=subprocess.PIPE, # Capture stderr + stdout=self.log_file, # Write to log file + stderr=subprocess.STDOUT, # Merge stderr into stdout preexec_fn=os.setsid, # Create new process group ) @@ -85,22 +113,23 @@ class InferenceClient: # Check if process died if self.server_process.poll() is not None: - # Capture and display error output - try: - stdout, stderr = self.server_process.communicate(timeout=1) - except subprocess.TimeoutExpired: - stdout, stderr = "", "" - + # Read error output from log file error_msg = f"Server failed to start (exit code: {self.server_process.returncode})" print(f"[FaceMask] ERROR: {error_msg}") - if stdout and stdout.strip(): - print("[FaceMask] Server stdout:") - print(stdout.strip()) - - if stderr and stderr.strip(): - print("[FaceMask] Server stderr:") - print(stderr.strip()) + try: + if self.log_file: + self.log_file.close() + with open(self.log_file_path, 'r') as f: + log_content = f.read() + if log_content.strip(): + print("[FaceMask] Server log:") + # Show last 50 lines + lines = log_content.strip().split('\n') + for line in lines[-50:]: + print(line) + except Exception as e: + print(f"[FaceMask] Could not read log file: {e}") self.server_process = None raise RuntimeError(error_msg) @@ -108,19 +137,21 @@ class InferenceClient: time.sleep(0.5) # If we get here, startup timed out - # Try to capture any partial output - if self.server_process: - try: - # Non-blocking read with short timeout - stdout, stderr = self.server_process.communicate(timeout=0.1) - if stdout and stdout.strip(): - print("[FaceMask] Server stdout (partial):") - print(stdout.strip()) - if stderr and stderr.strip(): - print("[FaceMask] Server stderr (partial):") - print(stderr.strip()) - except subprocess.TimeoutExpired: - pass # Process still running but not responding + print("[FaceMask] Server startup timed out") + + # Try to read partial log + try: + if self.log_file: + self.log_file.close() + with open(self.log_file_path, 'r') as f: + log_content = f.read() + if log_content.strip(): + print("[FaceMask] Server log (partial):") + lines = log_content.strip().split('\n') + for line in lines[-30:]: + print(line) + except Exception: + pass raise RuntimeError("Server startup timed out") @@ -136,6 +167,14 @@ class InferenceClient: pass finally: self.server_process = None + + # Close log file + if self.log_file: + try: + self.log_file.close() + except Exception: + pass + self.log_file = None def is_server_running(self) -> bool: """Check if server is responding."""