Poll a LangGraph server health endpoint until it responds. Args: url: Server base URL (health endpoint is `{url}/ok`). timeout: Max seconds to wait. process: Optional subprocess handle; if the process exits early we fail fast instead of waiting for the timeou
(
url: str,
*,
timeout: float = _HEALTH_TIMEOUT, # noqa: ASYNC109
process: subprocess.Popen | None = None,
read_log: Callable[[], str] | None = None,
local: bool = False,
)
| 224 | |
| 225 | |
| 226 | async def wait_for_server_healthy( |
| 227 | url: str, |
| 228 | *, |
| 229 | timeout: float = _HEALTH_TIMEOUT, # noqa: ASYNC109 |
| 230 | process: subprocess.Popen | None = None, |
| 231 | read_log: Callable[[], str] | None = None, |
| 232 | local: bool = False, |
| 233 | ) -> None: |
| 234 | """Poll a LangGraph server health endpoint until it responds. |
| 235 | |
| 236 | Args: |
| 237 | url: Server base URL (health endpoint is `{url}/ok`). |
| 238 | timeout: Max seconds to wait. |
| 239 | process: Optional subprocess handle; if the process exits early |
| 240 | we fail fast instead of waiting for the timeout. |
| 241 | read_log: Optional callable returning log file contents (for |
| 242 | error messages on early exit). |
| 243 | local: Use a shorter poll interval for local servers. |
| 244 | |
| 245 | Raises: |
| 246 | RuntimeError: If the server doesn't become healthy in time. |
| 247 | """ |
| 248 | import httpx |
| 249 | |
| 250 | poll_interval = ( |
| 251 | _HEALTH_POLL_INTERVAL_LOCAL if local else _HEALTH_POLL_INTERVAL_REMOTE |
| 252 | ) |
| 253 | health_url = f"{url}/ok" |
| 254 | deadline = time.monotonic() + timeout |
| 255 | last_status: int | None = None |
| 256 | last_exc: Exception | None = None |
| 257 | |
| 258 | async with httpx.AsyncClient() as client: |
| 259 | while time.monotonic() < deadline: |
| 260 | if process and process.poll() is not None: |
| 261 | output = read_log() if read_log else "" |
| 262 | msg = f"Server process exited with code {process.returncode}" |
| 263 | if output: |
| 264 | summary = _extract_startup_error_marker(output) |
| 265 | if summary: |
| 266 | msg += f": {summary}" |
| 267 | msg += f"\n{output[-_LOG_TAIL_CHARS:]}" |
| 268 | raise RuntimeError(msg) |
| 269 | |
| 270 | try: |
| 271 | resp = await client.get(health_url, timeout=2) |
| 272 | if resp.status_code == 200: # noqa: PLR2004 |
| 273 | logger.info("Server is healthy at %s", url) |
| 274 | return |
| 275 | last_status = resp.status_code |
| 276 | logger.debug("Health check returned status %d", resp.status_code) |
| 277 | except (httpx.TransportError, OSError) as exc: |
| 278 | logger.debug("Health check attempt failed: %s", exc) |
| 279 | last_exc = exc |
| 280 | |
| 281 | await asyncio.sleep(poll_interval) |
| 282 | |
| 283 | msg = f"Server did not become healthy within {timeout}s" |