One client disconnect closes the shared WASM guard and gives 502 for the rest of the session
Maintainers usually reply within 1 day
Assessment
This issue has not been assessed yet.
Description
Summary
The proxy calls the WASM guard with the HTTP request context (r.Context()). The wazero runtime uses WithCloseOnContextDone(true). When a client disconnects while its request waits for g.mu, the next guard call on that request uses a canceled context. wazero then closes the module that all requests share. isWasmTrap classifies the resulting sys.ExitError (exit code ExitCodeContextCanceled = 0xffffffff) as a trap, so the gateway sets g.failed = true. After that, every request gets HTTP 502 resource labeling failed for the rest of the session.
Thus one client that stops unexpectedly (crash, timeout, Ctrl-C) disables the proxy for all other clients.
Seen on v0.4.25 (CLI proxy mode, gh-aw-firewall 0.28.23). The code path is the same on v0.4.9 and on main (81d6a4d5).
Code path
internal/proxy/handler.gohandleWithDIFC:ctx := r.Context()goes toguard.RunPipelinePrePhasesandguard.RunPipelinePhase4.internal/guard/wasm_lifecycle.gocallWasmGuardFunction:g.mu.Lock(). Requests wait here one at a time. The context of a waiting request can be canceled while it waits.internal/guard/wasm_exec.gotryCallWasmFunction:g.wasmAlloc(ctx, …)callsallocwith the canceled context. WithWithCloseOnContextDone(true), wazero (v1.12.0,ModuleInstance.CloseModuleOnCanceledOrTimeout) closes the module and returnssys.ExitError"module closed with context canceled".internal/guard/wasm_lifecycle.goisWasmTrap:exitErr.ExitCode() != 0is true, socallWasmFunctionsetsg.failed = true.- All later calls return
WASM guard 'github' is unavailable after a previous trap.
The cleanup code already uses context.WithoutCancel(ctx) for dealloc, but alloc and the guard function call use the request context.
Evidence
A script sent 6 parallel gh api requests through the CLI proxy. Some gh processes in the CLI proxy container crashed with runtime: failed to create new OS thread (have 5 already; errno=11) (a separate thread-limit problem). One of them (a GraphQL pull_request_read) had a request in progress:
CLI proxy access.jsonl:
02:57:40.202Z exec_start api graphql (PR A)
02:57:40.580Z exec_done api graphql (PR A) exit=2 "runtime: failed to create new OS thread (have 5 already; errno=11)"
Gateway proxy.log: three GraphQL requests arrive together and wait for the guard lock. The request of the crashed client gets the lock about 640 ms after the client stopped:
02:57:40.382Z [proxy:handler] incoming POST /api/graphql
02:57:40.388Z [proxy:handler] incoming POST /api/graphql
02:57:40.390Z [proxy:handler] incoming POST /api/graphql
...
02:57:41.215Z [guard:wasm] label_resource input JSON: 98 bytes
02:57:41.216Z [guard:wasm] Using guard allocator path: guard=github
02:57:41.218Z [ERROR] [backend] WASM guard trap: guard=github, func=label_resource, error=failed to allocate WASM input buffer: module closed with context canceled
02:57:41.219Z [guard:wasm] callWasmFunction: guard=github is unavailable after a previous trap
02:57:41.222Z [ERROR] [proxy] Request rejected: status=502 code=bad_gateway message=resource labeling failed ...
There was one trap in the log, followed by 277 rejected requests until the run stopped. The two other waiting requests got the coarse-grained fallback in Phase 4. All later requests failed in Phase 1.
Proposed fix
Any of these prevents the failure. The first is the smallest change:
- Call the guard with
context.WithoutCancel(ctx)(or a new context with a short timeout) instead of the request context. A guard call is short and changes shared state, so a canceled client must not be able to stop it partway through. - Before
callWasmFunction, checkctx.Err()after the lock is acquired and return that error. Do not call into the module with a context that is already canceled. - In
isWasmTrap, treatsys.ExitCodeContextCanceledandsys.ExitCodeDeadlineExceededas host errors, and reinstantiate the module (theNewSessionGuardpath already does this) instead of settingg.failedfor the full session.
Related
- #3711: a guest panic disabled the guard for the full session. The failure mode is the same, but the trigger here is on the host side, and it applies to every guard.
- Dominant language
- Go
- Stars
- 176
- Forks
- 51
- Avg merge
- 6h 40m
- Merged PRs (30d)
- 258
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from github/gh-aw-mcpg
-
Difficulty 1/5 Under an hour Newbie friendliness 10/100
github/gh-aw-mcpg#14098 ·
Maintainers usually reply within 1 day
-
automation mcp-gateway test
Difficulty 1/5 Under an hour Newbie friendliness 10/100
github/gh-aw-mcpg#14097 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 10/100
github/gh-aw-mcpg#14093 ·
Maintainers usually reply within 1 day
-
automation repo-assist
Difficulty 1/5 Under an hour Newbie friendliness 20/100
github/gh-aw-mcpg#12406 ·
Maintainers usually reply within 1 day
-
automation mcp-gateway test
Difficulty 1/5 Under an hour Newbie friendliness 5/100
github/gh-aw-mcpg#6522 ·
Maintainers usually reply within 1 day
All issues in github/gh-aw-mcpg
Similar issues
-
bug needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
DataDog/dd-trace-go#5469 ·
Maintainers usually reply within 1 day
-
bug tests
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
l3montree-dev/devguard#3101 ·
Maintainers usually reply within 1 day
-
area:*of bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
oapi-codegen/oapi-codegen#2593 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 1/5 Under an hour Newbie friendliness 85/100
DaoCloud/DaoCloud-docs#7432 ·
Maintainers usually reply within 1 day