Blocks until the spawned engine answers /api/capabilities. Bounded by ``_READY_TIMEOUT_SECONDS``; on timeout (or child death) the child is reaped and a 503 with Retry-After is raised so clients know the engine is still warming up rather than broken.
(
process: subprocess.Popen, url: str, project: str, stderr_tail: deque
)
| 310 | |
| 311 | |
| 312 | def _wait_until_ready( |
| 313 | process: subprocess.Popen, url: str, project: str, stderr_tail: deque |
| 314 | ) -> None: |
| 315 | """Blocks until the spawned engine answers /api/capabilities. |
| 316 | |
| 317 | Bounded by ``_READY_TIMEOUT_SECONDS``; on timeout (or child death) the |
| 318 | child is reaped and a 503 with Retry-After is raised so clients know the |
| 319 | engine is still warming up rather than broken. |
| 320 | """ |
| 321 | deadline = time.monotonic() + _READY_TIMEOUT_SECONDS |
| 322 | last_error = "no response" |
| 323 | while time.monotonic() < deadline: |
| 324 | if process.poll() is not None: |
| 325 | error_lines = list(stderr_tail)[-5:] |
| 326 | raise HTTPException( |
| 327 | status_code=503, |
| 328 | detail=( |
| 329 | "tracedecay dashboard exited during startup for project " |
| 330 | f"{project!r}: " + (" / ".join(error_lines) or "no output") |
| 331 | ), |
| 332 | headers={"Retry-After": "5"}, |
| 333 | ) |
| 334 | try: |
| 335 | request = urllib.request.Request(f"{url}/api/capabilities", method="GET") |
| 336 | with urllib.request.urlopen(request, timeout=2.0) as response: |
| 337 | if response.status < 500: |
| 338 | return |
| 339 | last_error = f"HTTP {response.status}" |
| 340 | except Exception as exc: |
| 341 | last_error = str(exc) |
| 342 | time.sleep(_READY_POLL_INTERVAL_SECONDS) |
| 343 | _terminate_process(process) |
| 344 | raise HTTPException( |
| 345 | status_code=503, |
| 346 | detail=( |
| 347 | f"tracedecay dashboard for project {project!r} did not become " |
| 348 | f"ready within {_READY_TIMEOUT_SECONDS:.0f}s: {last_error}" |
| 349 | ), |
| 350 | headers={"Retry-After": "5"}, |
| 351 | ) |
| 352 | |
| 353 | |
| 354 | def _shutdown() -> None: |
no test coverage detected