From d026bbd4004678d67ec277ae0e558f2b172ce18d Mon Sep 17 00:00:00 2001 From: timmy Date: Sat, 8 Aug 2026 14:56:45 +0000 Subject: [PATCH] perf: isolate authentication database work (#311) --- src/main.py | 10 +-- tests/test_dashboard_auth.py | 165 +++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 5 deletions(-) diff --git a/src/main.py b/src/main.py index d80d9ba..2225af0 100644 --- a/src/main.py +++ b/src/main.py @@ -586,7 +586,7 @@ async def sign_in(payload: DashboardSignIn, request: Request, response: Response ) attempts = _login_attempt_store() try: - retry_after = attempts.retry_after(source) + retry_after = await asyncio.to_thread(attempts.retry_after, source) except LoginAttemptStoreError: return JSONResponse( {"detail": "Sign-in throttling is temporarily unavailable"}, @@ -607,7 +607,7 @@ async def sign_in(payload: DashboardSignIn, request: Request, response: Response payload.access_token, configured_token ): try: - attempts.record_failure(source) + await asyncio.to_thread(attempts.record_failure, source) except LoginAttemptStoreError: return JSONResponse( {"detail": "Sign-in throttling is temporarily unavailable"}, @@ -616,7 +616,7 @@ async def sign_in(payload: DashboardSignIn, request: Request, response: Response ) raise HTTPException(status_code=401, detail="Invalid access token") try: - attempts.clear(source) + await asyncio.to_thread(attempts.clear, source) except LoginAttemptStoreError: return JSONResponse( {"detail": "Sign-in throttling is temporarily unavailable"}, @@ -624,7 +624,7 @@ async def sign_in(payload: DashboardSignIn, request: Request, response: Response headers={"Cache-Control": "no-store"}, ) try: - signed, session = dashboard_auth.issue_session() + signed, session = await asyncio.to_thread(dashboard_auth.issue_session) except dashboard_auth.SessionStoreError: return JSONResponse( {"detail": "Session registry is temporarily unavailable"}, @@ -668,7 +668,7 @@ async def session_status(request: Request): async def sign_out(request: Request, response: Response): session = request.state.dashboard_session try: - dashboard_auth.revoke_session(session) + await asyncio.to_thread(dashboard_auth.revoke_session, session) except dashboard_auth.SessionStoreError: return JSONResponse( {"detail": "Session registry is temporarily unavailable"}, diff --git a/tests/test_dashboard_auth.py b/tests/test_dashboard_auth.py index 15a0b90..8cccc72 100644 --- a/tests/test_dashboard_auth.py +++ b/tests/test_dashboard_auth.py @@ -430,6 +430,171 @@ async def test_session_registry_latency_does_not_block_the_event_loop(access_con assert heartbeat_elapsed < 0.08 +@pytest.mark.anyio +async def test_sign_in_throttle_lookup_does_not_block_the_event_loop( + access_control, monkeypatch +): + class SlowAttempts: + def retry_after(self, source): + time.sleep(0.15) + return 17 + + monkeypatch.setattr(main, "_login_attempt_store", lambda: SlowAttempts()) + transport = httpx.ASGITransport(app=main.app, client=("203.0.113.10", 1234)) + async with httpx.AsyncClient(transport=transport, base_url="https://test") as client: + sign_in = asyncio.create_task( + client.post("/api/v1/session", json={"access_token": "wrong"}) + ) + await asyncio.sleep(0) + started = time.perf_counter() + await asyncio.sleep(0.01) + heartbeat_elapsed = time.perf_counter() - started + response = await sign_in + + assert response.status_code == 429 + assert response.headers["retry-after"] == "17" + assert heartbeat_elapsed < 0.08 + + +@pytest.mark.anyio +async def test_failed_sign_in_recording_does_not_block_the_event_loop( + access_control, monkeypatch +): + class SlowAttempts: + def retry_after(self, source): + time.sleep(0.02) + return 0 + + def record_failure(self, source): + time.sleep(0.15) + + monkeypatch.setattr(main, "_login_attempt_store", lambda: SlowAttempts()) + transport = httpx.ASGITransport(app=main.app, client=("203.0.113.11", 1234)) + async with httpx.AsyncClient(transport=transport, base_url="https://test") as client: + sign_in = asyncio.create_task( + client.post("/api/v1/session", json={"access_token": "wrong"}) + ) + await asyncio.sleep(0) + started = time.perf_counter() + await asyncio.sleep(0.04) + heartbeat_elapsed = time.perf_counter() - started + response = await sign_in + + assert response.status_code == 401 + assert heartbeat_elapsed < 0.08 + + +@pytest.mark.anyio +async def test_successful_sign_in_throttle_clear_does_not_block_the_event_loop( + access_control, monkeypatch +): + class SlowAttempts: + def retry_after(self, source): + time.sleep(0.02) + return 0 + + def clear(self, source): + time.sleep(0.15) + + monkeypatch.setattr(main, "_login_attempt_store", lambda: SlowAttempts()) + transport = httpx.ASGITransport(app=main.app, client=("203.0.113.12", 1234)) + async with httpx.AsyncClient(transport=transport, base_url="https://test") as client: + sign_in = asyncio.create_task( + client.post( + "/api/v1/session", + json={"access_token": "correct horse battery staple"}, + ) + ) + await asyncio.sleep(0) + started = time.perf_counter() + await asyncio.sleep(0.04) + heartbeat_elapsed = time.perf_counter() - started + response = await sign_in + + assert response.status_code == 200 + assert heartbeat_elapsed < 0.08 + + +@pytest.mark.anyio +async def test_session_activation_does_not_block_the_event_loop( + access_control, monkeypatch +): + class Attempts: + def retry_after(self, source): + time.sleep(0.02) + return 0 + + def clear(self, source): + return None + + class SlowStore: + def activate(self, session_id, expires_at): + time.sleep(0.15) + + monkeypatch.setattr(main, "_login_attempt_store", lambda: Attempts()) + monkeypatch.setattr(main.dashboard_auth, "_session_store", lambda now=None: SlowStore()) + transport = httpx.ASGITransport(app=main.app, client=("203.0.113.13", 1234)) + async with httpx.AsyncClient(transport=transport, base_url="https://test") as client: + sign_in = asyncio.create_task( + client.post( + "/api/v1/session", + json={"access_token": "correct horse battery staple"}, + ) + ) + await asyncio.sleep(0) + started = time.perf_counter() + await asyncio.sleep(0.04) + heartbeat_elapsed = time.perf_counter() - started + response = await sign_in + + assert response.status_code == 200 + assert any( + "stackchain_session=" in value + for value in response.headers.get_list("set-cookie") + ) + assert heartbeat_elapsed < 0.08 + + +@pytest.mark.anyio +async def test_single_session_revocation_does_not_block_the_event_loop( + access_control, monkeypatch +): + transport = httpx.ASGITransport(app=main.app) + async with httpx.AsyncClient(transport=transport, base_url="https://test") as client: + await client.post( + "/api/v1/session", + json={"access_token": "correct horse battery staple"}, + ) + csrf = client.cookies["stackchain_csrf"] + + class SlowStore: + def is_active(self, session_id, expires_at): + time.sleep(0.02) + return True + + def revoke(self, session_id): + time.sleep(0.15) + + monkeypatch.setattr( + main.dashboard_auth, "_session_store", lambda now=None: SlowStore() + ) + sign_out = asyncio.create_task( + client.delete( + "/api/v1/session", + headers={"Origin": "https://test", "X-CSRF-Token": csrf}, + ) + ) + await asyncio.sleep(0) + started = time.perf_counter() + await asyncio.sleep(0.04) + heartbeat_elapsed = time.perf_counter() - started + response = await sign_out + + assert response.status_code == 200 + assert response.json()["authenticated"] is False + assert heartbeat_elapsed < 0.08 + + @pytest.mark.anyio async def test_session_registry_read_failure_fails_closed_before_gitea( access_control, monkeypatch -- 2.43.0