Spaces:
Running on Zero
Running on Zero
| """Stdlib HTTP wrapper + lifecycle watchdog for the inference engine.""" | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import threading | |
| import time | |
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
| def route(engine, method, path, body): | |
| """Pure request router over a loaded engine. Returns (status, json-able dict).""" | |
| if method == "GET" and path == "/health": | |
| return 200, {"status": "ok", **engine.levels()} | |
| if method == "POST" and path == "/predict": | |
| vel = engine.predict(body) | |
| return 200, {"velocities": {str(k): v for k, v in vel.items()}} | |
| return 404, {"error": "not found"} | |
| def pid_alive(pid): | |
| if pid is None: | |
| return True | |
| try: | |
| os.kill(int(pid), 0) | |
| except ProcessLookupError: | |
| return False | |
| except PermissionError: | |
| return True # exists but not owned by us | |
| return True | |
| def should_exit(now, last_request, idle_timeout, parent_alive): | |
| if not parent_alive: | |
| return True | |
| if idle_timeout and (now - last_request) > idle_timeout: | |
| return True | |
| return False | |
| def _make_handler(engine, state): | |
| class Handler(BaseHTTPRequestHandler): | |
| def log_message(self, *a): # silence default stderr logging | |
| pass | |
| def _send(self, status, payload): | |
| data = json.dumps(payload).encode() | |
| self.send_response(status) | |
| self.send_header("Content-Type", "application/json") | |
| self.send_header("Content-Length", str(len(data))) | |
| self.end_headers() | |
| self.wfile.write(data) | |
| def do_GET(self): | |
| try: | |
| status, body = route(engine, "GET", self.path, None) | |
| except Exception as e: # noqa: BLE001 | |
| status, body = 500, {"error": str(e)} | |
| self._send(status, body) | |
| def do_POST(self): | |
| state["last_request"] = time.monotonic() | |
| try: | |
| n = int(self.headers.get("Content-Length", 0)) | |
| body = json.loads(self.rfile.read(n) or b"{}") | |
| status, out = route(engine, "POST", self.path, body) | |
| except Exception as e: # noqa: BLE001 - report to client | |
| status, out = 500, {"error": str(e)} | |
| self._send(status, out) | |
| return Handler | |
| def run(engine, port=8765, parent_pid=None, idle_timeout=1800): | |
| state = {"last_request": time.monotonic()} | |
| httpd = ThreadingHTTPServer(("127.0.0.1", port), _make_handler(engine, state)) | |
| def watchdog(): | |
| while True: | |
| time.sleep(5) | |
| if should_exit(time.monotonic(), state["last_request"], idle_timeout, pid_alive(parent_pid)): | |
| os._exit(0) | |
| threading.Thread(target=watchdog, daemon=True).start() | |
| print(f"Dynamics Needed engine on http://127.0.0.1:{port} (parent={parent_pid})") | |
| httpd.serve_forever() | |