{{ message }}
chore(server): set uvicorn's keep-alive explicitly instead of inheriting 5s - #1561
Draft
wanghy73 wants to merge 1 commit into
Draft
chore(server): set uvicorn's keep-alive explicitly instead of inheriting 5s#1561wanghy73 wants to merge 1 commit into
wanghy73 wants to merge 1 commit into
Conversation
wanghy73
marked this pull request as draft
August 31, 2026 23:58
Author
…ing 5s uvicorn defaults timeout_keep_alive to 5 seconds and app.py never set it, which is shorter than the idle timeout of every client that fronts this server: Go's transport pools idle connections for 90s, httpx for 5s. A server keep-alive shorter than its clients' is a known hazard - a client can reuse a connection the server has just closed, and because POST is not idempotent it will not retry, so it waits out its own timeout instead. Setting it explicitly, above the largest client idle timeout in play, removes that hazard. MEMMACHINE_KEEP_ALIVE_SECONDS overrides it. This does NOT fix the 30-second stalls observed on this deployment and must not be merged as though it does. Raising the value removed them from one load arm and not from another on the same image: 128 users, no think-time 242 stalls -> 0 100 users, 2s/5s think-times 32 stalls -> 124 The change was verified live in the running container for both, so that is not a deployment mistake. The two arms differ in think-times as well as in this setting, so the arm that showed zero may simply never have left a connection idle long enough to trigger the fault. The cause is unknown, and the stall is still present at 0.4% of requests. An earlier version of this commit claimed the first result as a confirmed fix. That was drawn from a single arm and was wrong. Not measured: holding connections open 24x longer keeps more sockets and file descriptors alive per idle client. Immaterial at the concurrency tested here, unknown at larger client counts. ruff check and ruff format clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nr9kacmpFVTTfkZRw6esxP
wanghy73
force-pushed
the
fix/uvicorn-keepalive
branch
from
September 1, 2026 00:16
9b23fc8 to
db6f87b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What
uvicorndefaultstimeout_keep_aliveto 5 seconds andapp.pynever set it. This sets it explicitly to 120 s, overridable viaMEMMACHINE_KEEP_ALIVE_SECONDS.Why
A server keep-alive shorter than its clients' idle timeouts is a known hazard. Every client that fronts this server pools idle connections for longer than 5 s — Go's transport for 90 s, httpx for 5 s. A client can reuse a connection the server has just closed, and because POST is not idempotent it will not retry: it waits out its own timeout instead.
Setting the value above the largest client idle timeout in play removes that hazard.
What this does NOT do
It does not fix the 30-second stalls seen on this deployment, and should not be merged as though it does. Raising the value removed them from one load arm and not from another on the same image:
The change was verified live inside the running container in both cases (
KEEP_ALIVE_SECONDS = 120,timeout_keep_alivepresent instart_http), so this is not a deployment mistake.The two arms differ in think-times as well as in this setting, so the arm that showed zero may simply never have left a connection idle long enough to trigger the fault. The cause is unknown and the stall is still present at 0.4% of requests.
An earlier version of this PR claimed the first result as a confirmed fix. That was drawn from a single arm and was wrong.
The stall itself, for whoever picks it up
Still unexplained, and worth more than its rate suggests — each stall occupies a client slot for the full 30 s, which consumed roughly 47% of capacity in the 128-user arm.
restyTimeoutin MemMachine-Platforminternal/memmachine/core/client.go/healthGETs stay green at ~1 ms throughoutNote that
AccessLogMiddlewarelogs aftercall_nextreturns, so a missing log line alone does not prove a request never arrived — the duration histogram is what rules out an internal stall.Not measured
Holding connections open 24× longer keeps more sockets and file descriptors alive per idle client. Immaterial at the concurrency tested here; unknown at larger client counts.
ruff checkandruff formatclean.🤖 Generated with Claude Code
https://claude.ai/code/session_01Nr9kacmpFVTTfkZRw6esxP