Bound readiness probe latency #72
16
src/main.py
16
src/main.py
|
|
@ -15,6 +15,7 @@ from src.views import router as frontend_router
|
||||||
app = FastAPI(title="Stackchain Dashboard")
|
app = FastAPI(title="Stackchain Dashboard")
|
||||||
CONTEXT_TIMEOUT_SECONDS = 5.0
|
CONTEXT_TIMEOUT_SECONDS = 5.0
|
||||||
EVENT_STREAM_TIMEOUT_SECONDS = 5.0
|
EVENT_STREAM_TIMEOUT_SECONDS = 5.0
|
||||||
|
READINESS_TIMEOUT_SECONDS = 5.0
|
||||||
FRONTEND_DIR = Path(__file__).resolve().parent.parent / "frontend"
|
FRONTEND_DIR = Path(__file__).resolve().parent.parent / "frontend"
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
|
|
@ -39,15 +40,26 @@ def health() -> dict[str, str]:
|
||||||
async def readiness():
|
async def readiness():
|
||||||
"""Return readiness after verifying the configured Gitea connection."""
|
"""Return readiness after verifying the configured Gitea connection."""
|
||||||
try:
|
try:
|
||||||
user = await current_user()
|
user = await asyncio.wait_for(
|
||||||
|
current_user(), timeout=READINESS_TIMEOUT_SECONDS
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
timed_out = isinstance(exc, TimeoutError)
|
||||||
|
error_message = (
|
||||||
|
f"Gitea readiness check timed out after {READINESS_TIMEOUT_SECONDS:g}s"
|
||||||
|
if timed_out
|
||||||
|
else str(exc)
|
||||||
|
)
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
{
|
{
|
||||||
"status": "not_ready",
|
"status": "not_ready",
|
||||||
"service": "stackchain-dashboard",
|
"service": "stackchain-dashboard",
|
||||||
"error": str(exc),
|
"error": error_message,
|
||||||
},
|
},
|
||||||
status_code=503,
|
status_code=503,
|
||||||
|
headers={
|
||||||
|
"Retry-After": str(max(1, math.ceil(READINESS_TIMEOUT_SECONDS)))
|
||||||
|
} if timed_out else None,
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"status": "ready",
|
"status": "ready",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from src import main
|
from src import main
|
||||||
|
|
@ -37,3 +40,29 @@ async def test_readiness_endpoint_returns_503_when_gitea_is_unavailable(monkeypa
|
||||||
assert response.status_code == 503
|
assert response.status_code == 503
|
||||||
assert b'"status":"not_ready"' in response.body
|
assert b'"status":"not_ready"' in response.body
|
||||||
assert b'"error":"connection refused"' in response.body
|
assert b'"error":"connection refused"' in response.body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_readiness_endpoint_times_out_and_cancels_stalled_gitea_check(monkeypatch):
|
||||||
|
cancelled = asyncio.Event()
|
||||||
|
|
||||||
|
async def hanging_user():
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(0.05)
|
||||||
|
return {"login": "too-late"}
|
||||||
|
finally:
|
||||||
|
cancelled.set()
|
||||||
|
|
||||||
|
monkeypatch.setattr(main, "READINESS_TIMEOUT_SECONDS", 0.01, raising=False)
|
||||||
|
monkeypatch.setattr(main, "current_user", hanging_user)
|
||||||
|
|
||||||
|
response = await main.readiness()
|
||||||
|
|
||||||
|
assert response.status_code == 503
|
||||||
|
assert response.headers["retry-after"] == "1"
|
||||||
|
assert json.loads(response.body) == {
|
||||||
|
"status": "not_ready",
|
||||||
|
"service": "stackchain-dashboard",
|
||||||
|
"error": "Gitea readiness check timed out after 0.01s",
|
||||||
|
}
|
||||||
|
assert cancelled.is_set()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user