Repository navigation
feat(settings): time out opening a database connection - #215
Merged
Merged
Conversation
Opening a database connection had no time limit of its own, so psycopg used its default of 130 s. Against a Postgres that accepts the connection and never answers, a request then outlasted gunicorn's 120 s worker timeout, and ended with the worker killed instead of an error. DJANGO_DB_CONNECT_TIMEOUT sets libpq's connect_timeout in the database OPTIONS, applied to each address the host resolves to: 10 s by default, 2 to 600 accepted. psycopg turns anything below 2 into 2, and 0 or less into its own 130 s, so those values refuse to start rather than mean something unexpected. Blank means the default, as an env file copied from .env.example may have it. Tested against a listening socket that never answers, through Django's own connection code: the connection fails after about 2 s. Documented in .env.example, docs/production_image.md, and the health endpoint's notes, which said opening the connection had no limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoXLf4KSfqzMFrdZGQG9Up
- The behavioural test now also checks that Django's own connection parameters, built from the unmodified settings, carry the configured timeout, so it covers the wiring in base.py as well as psycopg keeping to it. - Docs: against a silent database, /api/health answers 503 after about the timeout per address, so a probe with a shorter timeout times out instead; either way it fails. - base.py and .env.example say that the setting, always passed, makes libpq's PGCONNECT_TIMEOUT ineffective. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoXLf4KSfqzMFrdZGQG9Up
bryangingechen
marked this pull request as ready for review
October 11, 2026 22:10
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.
Motivation
Opening a database connection had no time limit of its own, so psycopg used its built-in default of 130 s. Against a Postgres that accepts the TCP connection and then never answers, a request waited past gunicorn's 120 s worker timeout and ended with the worker killed rather than an error. The health endpoint's notes recorded this gap. Celery tasks waited the same way.
What changes
DJANGO_DB_CONNECT_TIMEOUT, wired throughbase.pyand.env.example, sets libpq'sconnect_timeoutinDATABASES["default"]["OPTIONS"]. It applies to each address the host name resolves to..env.examplemay have it.PGCONNECT_TIMEOUTno longer has any effect. Nothing in the repo used it.docs/production_image.md(Configuration, and the/api/healthbullet: against a database that doesn't answer, the 503 comes after about the timeout, so a probe with a shorter timeout times out instead, and fails either way). The health endpoint's notes inqb_site/api/AGENTS.mdandhealth.pynow name the setting instead of describing the gap.User-visible effects
None while the database answers. When it hangs, requests and tasks fail after about 10 s instead of 130 s. No migrations.
Testing
core.tests.test_db_connect_timeout(5 tests):0,1,-5,601,tenand2.5refuse to start, with the setting's name in the error;OPTIONS, and Django's own connection parameters, built from them, pass it to psycopg;OPTIONSand a 2 s timeout, raisesOperationalErrorafter about 2 s, where it would otherwise wait for psycopg's 130 s.uv run ruff check .,uv run ruff format --check .OPTIONSthrough, and that psycopg 3.2.10 and 3.3.6 both apply the timeout per attempt, with the same 2 s floor and 130 s fallback. The review fixes are in8eede328. Left for a follow-up: a server that freezes after connecting still holds a request until gunicorn's timeout. TCP keepalives ortcp_user_timeoutwould cover that.bash scripts/repo_check_compose.sh: all 13 steps passed locally (core 119, syncer 543, analyzer 606, api 28, zulip_bot 350, console 96, site_analytics 81 tests). Compose's.envfrom.env.examplesets the new value, and every service connects with it.🤖 Generated with Claude Code
https://claude.ai/code/session_01QoXLf4KSfqzMFrdZGQG9Up