Returns the base URL of the tracedecay dashboard server, starting it on first use unless an external URL is configured. Spawn failures are cached for ``_SPAWN_RETRY_BACKOFF_SECONDS`` so a persistently failing spawn (e.g. project root not tracedecay-initialized) fails fast with a cle
()
| 370 | |
| 371 | |
| 372 | def _upstream_base() -> str: |
| 373 | """Returns the base URL of the tracedecay dashboard server, starting it |
| 374 | on first use unless an external URL is configured. |
| 375 | |
| 376 | Spawn failures are cached for ``_SPAWN_RETRY_BACKOFF_SECONDS`` so a |
| 377 | persistently failing spawn (e.g. project root not tracedecay-initialized) |
| 378 | fails fast with a clear 503 instead of serializing every request behind a |
| 379 | repeated ``_SPAWN_TIMEOUT_SECONDS`` spawn attempt under the module lock. |
| 380 | """ |
| 381 | configured = _env("DASHBOARD_URL") |
| 382 | if configured: |
| 383 | return configured.rstrip("/") |
| 384 | global _base_url, _last_spawn_failure |
| 385 | with _lock: |
| 386 | if _base_url is not None and _process is not None and _process.poll() is None: |
| 387 | return _base_url |
| 388 | if _last_spawn_failure is not None: |
| 389 | failed_at, detail = _last_spawn_failure |
| 390 | remaining = _SPAWN_RETRY_BACKOFF_SECONDS - (time.monotonic() - failed_at) |
| 391 | if remaining > 0: |
| 392 | raise HTTPException( |
| 393 | status_code=503, |
| 394 | detail=( |
| 395 | f"tracedecay dashboard spawn failed recently; retrying in " |
| 396 | f"{remaining:.0f}s. Last error: {detail}" |
| 397 | ), |
| 398 | ) |
| 399 | _last_spawn_failure = None |
| 400 | # Stale-instance reap: clear any previous (dead or live) child before |
| 401 | # spawning a replacement. |
| 402 | _shutdown() |
| 403 | try: |
| 404 | _base_url = _spawn_dashboard() |
| 405 | except HTTPException as exc: |
| 406 | _last_spawn_failure = (time.monotonic(), str(exc.detail)) |
| 407 | raise |
| 408 | return _base_url |
| 409 | |
| 410 | |
| 411 | def _proxy(method: str, upstream_path: str, request: Request, body: bytes | None) -> JSONResponse: |
no test coverage detected