mirror of
https://github.com/sredevopsorg/deepseek-kubernetes-sandbox.git
synced 2026-11-03 01:13:24 +00:00
fix(gateway): answer client-aborted requests with 499, not a traceback (#32)
* fix(gateway): answer client-aborted requests with 499, not a traceback A browser reload, back navigation or proxy timeout can abort a request while its body is still arriving. Starlette then raises ClientDisconnect from request.stream(); nothing caught it, so ServerErrorMiddleware logged a full ERROR traceback per aborted request and buried real faults. The request never reaches DSH (the body is buffered before the upstream send) and the peer can no longer be answered, so this is not an error. - Register a ClientDisconnect exception handler that answers 499 (CLIENT_CLOSED_REQUEST). One handler covers the proxy and the body-reading control routes (signup, login). A request already streaming keeps its upstream status because the response has started. - Make _release await-free, and reach it even when the upstream close is cancelled. CancelledError is a BaseException, so suppress(Exception) around upstream.aclose() skipped the release and stranded the in-flight slot, leaving that user's sandbox looking busy forever (the idle sweeper then never suspends it). - Add raw-ASGI regression tests: mid-body abort on the proxy and on signup, mid-stream abort, a cancelled release, and a cancelled upstream close. - Document the 499 semantics in the gateway README failure table and the architecture failure modes. Verified: make check, make test (114 passed), make lint. Against the pinned uvicorn 0.54 the abort reproduces the original traceback before the fix and logs no traceback after it. * chore(deps): bump gateway starlette to 1.10.0 and ruff to 0.17.2 Absorbs Dependabot PR #27 against current main: that branch predates the dsh-v0.2.1-rc.5 changelog/release commits, so merging it as-is would have reverted them. starlette 1.10.0 reworks URL-encoded form parsing (fields are built in the parser callbacks, and the last field is kept when the stream ends without an empty chunk) and clears tracebacks of handled exceptions. The gateway reads JSON and form bodies, so the existing suite exercises signup/login forms and the 499 client-abort path against the new release. ruff 0.17.2 only adds preview and fix-safety changes; the configured rule set reports nothing new. Verified: make check, make test (114 passed), make lint, make gateway (image builds on python:3.12-slim and imports starlette 1.10.0), plus a direct ASGI abort probe under 1.10.0 (499, in-flight slot released, nothing forwarded). --------- Co-authored-by: SREDevOpsAgents <agents@sredevops.org>
This commit is contained in:
committed by
GitHub
parent
8c34f43789
commit
cbcc56f97e
@@ -135,6 +135,8 @@ oauth2-proxy's egress to the gateway, DNS and public HTTPS.
|
||||
| Missing or invalid credentials | `401` |
|
||||
| Cross-origin cookie mutation | `403` |
|
||||
| Request body over `MAX_BODY_MB` | `413` |
|
||||
| Client aborts mid-body, before the response starts | `499` (`CLIENT_CLOSED_REQUEST`). Nothing is forwarded to DSH and nothing can be delivered; it is not an error, so no traceback is logged. Covers the body-reading control routes (`POST /users`, `POST /login`) as well as the proxy. |
|
||||
| Client aborts mid-stream (SSE, long response) | The response already started, so its upstream status stands (typically `200`); the stream simply ends. No traceback, and the in-flight slot is released as the relay unwinds. |
|
||||
| Sandbox not provisioned, wake timeout, handshake failure | `503` + `Retry-After: 10` |
|
||||
| Upstream connection/protocol failure | `502` |
|
||||
| WS auth failure (unknown user, bad token, cross-origin) | accept, then close `4401` |
|
||||
@@ -144,6 +146,12 @@ A close code can only be delivered after the WebSocket upgrade is accepted;
|
||||
Starlette would turn an early close into an HTTP 403, which is why WS auth
|
||||
failures are `101`, then close `4401`.
|
||||
|
||||
An aborted request is deliberately silent: uvicorn runs with `--no-access-log`,
|
||||
and the `499` cannot reach a peer that has already left (uvicorn's `send()` is
|
||||
a no-op once the socket is closed). Sandbox resolution, the wake and the DSH
|
||||
handshake run before the body is read, so an aborted upload can cost a wake or
|
||||
a handshake but never reaches DSH.
|
||||
|
||||
## Configuration
|
||||
|
||||
All values are read from the environment by `Settings.from_env()`; every one
|
||||
|
||||
Reference in New Issue
Block a user