fix: harden Postgres async engine with pool_recycle and command_timeout to stop stale-connection 504s#4230
Open
mvanhorn wants to merge 2 commits into
Open
Conversation
…ut to stop stale-connection 504s
fancyboi999
suggested changes
Jul 16, 2026
fancyboi999
left a comment
Collaborator
There was a problem hiding this comment.
Thanks @mvanhorn. I found one issue that should be addressed before this is ready.
Reviewed base SHA: b3a0dac8ad191fbfa248c0a1cecc8f2deeaa1c7d
Reviewed head SHA: e7b19240c556eae5edb30e62beca964201457c1d
[P1] Keep the database timeout inside the proxy deadline
- Location:
backend/packages/harness/deerflow/persistence/engine.py:22 - Problem:
POSTGRES_COMMAND_TIMEOUT_SECONDSis fixed at 60 seconds, which is the same deadline nginx applies to ordinary/api/*routes. A stale checkout or pre-ping that reaches this timeout still loses the race to nginx, so clients continue to receive the 504 this PR is meant to prevent. The constant also imposes an unconfigurable 60-second cap on every app-ORM command, including legitimate long-running operations. - Evidence: the Docker nginx locations for
/api/agents,/api/skills, and the/api/catch-all do not overrideproxy_read_timeout, so nginx uses its 60-second default. The issue evidence recordsrt=60.000andrt=60.034504s. The locked asyncpg 0.31.0 source definescommand_timeoutas the default timeout for operations on the connection, so setting it to the same 60-second boundary leaves no time for Gateway to turn the database timeout into an upstream response. - Suggested fix: make the app-ORM command timeout a
databasesetting and choose a default comfortably below the proxy deadline (for example 30 seconds), while keeping the recycle policy. Preserve an explicit opt-out or larger override for deployments that intentionally run longer database commands. - Test: add a regression that stalls the asyncpg ping/command past the configured database deadline and asserts checkout/request handling terminates before nginx's 60-second boundary; also cover the config override so a legitimate longer timeout is not silently clamped.
…se setting Default the app-ORM command timeout to 30s (below nginx's 60s proxy deadline), expose it as database.command_timeout with null to disable, and add regression coverage. Addresses P1 review feedback. Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Contributor
Author
|
Fixed in 3087049. The app-ORM command timeout is now a |
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.
Fixes #3882
Why
The reported 504s came from a stale half-open Postgres connection hanging at SQLAlchemy pool checkout. The async engine sets
pool_pre_ping=Truebut has nopool_recycleand no asyncpgcommand_timeout/keepalive, so a connection killed server-side can wedge a request until timeout.What changed
The Postgres async engine is hardened with
pool_recycleand an asyncpgcommand_timeoutvia a shared helper applied at bothcreate_async_enginecall sites, so stale connections are recycled instead of hanging.Surface area
Screenshots / Recording
N/A - backend connection-pool hardening.
Bug fix verification
pool_recycleandcommand_timeoutat both call sites.Validation
Ran the backend engine/persistence tests covering the new configuration.
AI assistance
Tool(s) used: Codex
How you used it: Implemented the fix from a written plan (root cause pinned to the engine config); I reviewed the diff before submission.