Sporalize-api / start.py
Shoraky's picture
Wait for analysis readiness before starting API
5208038
Raw History Blame Contribute Delete
2.94 kB
"""Run the public Node API and private Python worker as one service."""
import json
import os
from pathlib import Path
import signal
import subprocess
import sys
import time
from urllib.request import urlopen
from dotenv import load_dotenv
def wait_for_analysis(process, should_stop, timeout_seconds=25 * 60):
deadline = time.monotonic() + timeout_seconds
while not should_stop():
exit_code = process.poll()
if exit_code is not None:
print(f"Analysis process exited before it was ready ({exit_code})", flush=True)
return False
try:
with urlopen("http://127.0.0.1:8000/healthz", timeout=1) as response:
payload = json.load(response) if response.status == 200 else None
if isinstance(payload, dict) and payload.get("ok") is True:
return process.poll() is None
except (OSError, ValueError):
pass
if time.monotonic() >= deadline:
print(f"Analysis process did not become ready within {timeout_seconds} seconds", flush=True)
return False
time.sleep(0.5)
return False
def main():
root = Path(__file__).resolve().parent
load_dotenv(root / ".env")
port = int(os.environ.get("PORT", "4000"))
if not 1 <= port <= 65535 or port == 8000:
raise ValueError("PORT must be between 1 and 65535 and differ from AI port 8000")
env = dict(os.environ, PORT=str(port),
ANALYSIS_API_URL="http://127.0.0.1:8000",
BACKEND_INTERNAL_URL=f"http://127.0.0.1:{port}")
children = []
stopping = False
def stop(signum, frame):
nonlocal stopping
stopping = True
for sig in (signal.SIGINT, signal.SIGTERM):
signal.signal(sig, stop)
try:
analysis = subprocess.Popen([sys.executable, "-m", "uvicorn", "app:app",
"--host", "127.0.0.1", "--port", "8000", "--workers", "1"], cwd=root, env=env)
children.append(analysis)
if not wait_for_analysis(analysis, lambda: stopping):
return 0 if stopping else 1
children.append(subprocess.Popen(["node", "dist/server.js"], cwd=root, env=env))
while not stopping:
for child in children:
if child.poll() is not None:
print(f"Backend process {child.pid} exited ({child.returncode}); stopping service", flush=True)
return child.returncode or 1
time.sleep(0.25)
return 0
finally:
for child in children:
if child.poll() is None:
child.terminate()
deadline = time.monotonic() + 15
for child in children:
try:
child.wait(timeout=max(0, deadline - time.monotonic()))
except subprocess.TimeoutExpired:
child.kill()
child.wait()
if __name__ == "__main__":
sys.exit(main())