diff --git a/gateway/platforms/api_server_fire_startup.py b/gateway/platforms/api_server_fire_startup.py index 9041d070d3..788da19225 100644 --- a/gateway/platforms/api_server_fire_startup.py +++ b/gateway/platforms/api_server_fire_startup.py @@ -14,10 +14,10 @@ from typing import Any, Optional from aiohttp import web -# How long a fire that lands during startup waits before it is refused as retryable. A cold boot -# publishes its adapters within seconds; the bound keeps a gateway that never finishes starting from -# holding the request open (NAS's callback timeout is 30s). -FIRE_STARTUP_WAIT_SECONDS = 10.0 +# How long a fire that lands during startup waits before it is refused as retryable. Kept under the +# dashboard forwarder's 10s timeout: past it NAS sees a 503 while this handler still runs the job, and +# the retry would run it twice. +FIRE_STARTUP_WAIT_SECONDS = 7.0 _POLL_SECONDS = 0.1 @@ -25,12 +25,12 @@ async def refuse_until_started(runner: Any, job_id: str) -> Optional["web.Respon """None once ``runner`` has finished starting (``_running``: every platform connect attempted and its adapters published), or when there is no runner to ask (a self-hosted api_server, a test double). Otherwise the retryable 503, worded like the dashboard's own "gateway unreachable" so NAS classifies - it the same.""" + it the same, with the dashboard's 60s Retry-After (``_CRON_FIRE_RETRY_AFTER_SECONDS``).""" loop = asyncio.get_running_loop() deadline = loop.time() + FIRE_STARTUP_WAIT_SECONDS while runner is not None and not getattr(runner, "_running", True): if loop.time() >= deadline: return web.json_response( - {"error": "gateway unreachable; retry", "job_id": job_id}, status=503, headers={"Retry-After": "2"}) + {"error": "gateway unreachable; retry", "job_id": job_id}, status=503, headers={"Retry-After": "60"}) await asyncio.sleep(_POLL_SECONDS) return None diff --git a/tests/gateway/test_cron_fire_startup_gate.py b/tests/gateway/test_cron_fire_startup_gate.py index 1d9ccb94ca..71e70941bf 100644 --- a/tests/gateway/test_cron_fire_startup_gate.py +++ b/tests/gateway/test_cron_fire_startup_gate.py @@ -98,7 +98,9 @@ async def test_fire_is_retryable_and_never_claimed_when_startup_does_not_finish( resp = await _post_fire(adapter, runner) - assert resp.status == 503 + # NAS classifies this refusal by the exact body string and honours Retry-After. + assert resp.status == 503 and resp.headers["Retry-After"] == "60" + assert (await resp.json())["error"] == "gateway unreachable; retry" assert provider.claimed == [] and provider.fired == [] # nothing durably claimed for a fire we refused