Repository navigation
v0.3.6 — replay a large request body on failover
What's in v0.3.6
Patch release on top of v0.3.5. No wire-format or configuration changes —
one gateway bug fix, one routing refinement, and a test-harness fix.
A large request body can now fail over (the multi-minute silent turn)
Pingora keeps at most 64 KiB of the client body for a replay and silently drops
the rest. A real agent request is much larger — codex sends ~100 KB of
instructions and tool schemas — so any failover after the body had been
consumed handed the next upstream a truncated body. The upstream waited for the
rest, the client waited for a response, and neither side ever moved:
1794062827116 status 0 dur=391337ms coding_home_olefedora_gpu0 /v1/responses
Pingora only hands the body to request_body_filter when it has a retry buffer
or the body is empty (if buffer.is_some() || session.is_body_empty()), and
get_retry_buffer() returns None exactly when the buffer truncated — so for a
large body the filter was never called at all.
pingora-proxy 0.8.0 (and 0.9.1) still has that condition, so it is vendored in
vendor/pingora-proxy with one added term: when the downstream body is already
complete, the filter is driven once with an empty final chunk. The gateway keeps
the client body itself and re-feeds it whole, re-translated for the provider
that now answers (whose model name differs). See vendor/README.md and
vendor/pingora-proxy-maxllm.patch.
Measured after the fix:
94 KB body, plain route 200 in 0.82 s (was: hang, 391 s)
94 KB body, forced failover 200 in 2.94 s (403 primary → 200 fallback)
A body-level auto sentinel is rewritten in place
On a route without auto_model the sentinel only exists in the body
({"model": "auto"}), and the provider has to be picked before the body is
read. When the picked provider is itself a pool member — a pinned session, or a
route whose own provider is listed in the pool — the body is now rewritten to
that entry's model instead of forwarding the literal sentinel. Previously the
upstream rejected it and the request failed over to a different member on
every turn, throwing away the upstream prompt cache for the conversation; and a
member that does not validate model names (an Ollama / llama.cpp style server)
would silently serve whatever model it happened to host.
Tests
- the integration harness now claims gateway ports inside the test binary and
fails loudly when a gateway dies during startup —portpickerhanded the
same port to two parallel tests, so a test could read a 200 from another
test's gateway (random failures in whichever test lost the race); - the unclassifiable-429 test pins an unreachable laya endpoint instead of
silently using the developer's own decision server on port 18405.
cargo test --workspace green (5 consecutive integration runs),
cargo clippy --workspace --all-targets -- -D warnings clean.
Upgrading
No config change. Replace the binary and restart:
sha256sum maxllm-server-linux-x86_64-musl
# bc09f0c52fc59805c6a6cef5df33638b5f396f95ede3aec43e10893ccfb775a8
systemctl --user restart maxllm