Skip to content

Retry a query once when MySQL closed it for idling - #530

Merged
jarstelfox merged 3 commits into
masterfrom
db-idle-timeout
Oct 20, 2026
Merged

jarstelfox merged 3 commits into
masterfrom
db-idle-timeout

Conversation

@jarstelfox

@jarstelfox jarstelfox commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

🤖

Prod MySQL closes a connection after 60 seconds without a query (@@wait_timeout = 60), and the pool only finds out when the next query on that connection fails:

Error: The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout ...
errno: 4031

Prod logged it 6 times between Oct 16 and 20. Two were PR saves (ifixit#63906 and an ops PR) that only landed on a later update.

db.query now retries once on errno 4031. The server closed the connection before the query arrived, so the query never ran, and the pool discards that connection on the error. So the retry gets a fresh connection and is safe for writes too. Nothing else is retried: a connection lost mid-query might have run its write.

Why not mysql2's idleTimeout (this PR's first commit)

Review found that setting idleTimeout alone does nothing. mysql2 runs its idle check only while maxIdle is below connectionLimit (lib/base/pool.js), and both default to 10. Turning the check on starts a timer that keeps Node alive, so every test and bin that imports the DB without db.end() hangs. The suite hung when I tried it.

test/db-retry.test.js: errno 4031 runs the query once more; another error, or a second 4031, is passed on.

🤖 Generated with Claude Code

jarstelfox and others added 2 commits October 20, 2026 14:19
Prod MySQL closes a connection after 60 seconds without a query
(wait_timeout = 60), and mysql2 drops an idle pool connection only after
its own idleTimeout, which also defaults to 60 seconds. On that tie the
pool hands out a connection the server already closed, and the query
fails with errno 4031. Between Oct 16 and 20 prod logged it 6 times,
twice on PR saves (ifixit#63906 and an ops PR) that only landed on a
later update.

Dropping idle connections at 30 seconds, half the server's limit, means
the pool always lets go first. TCP keepalive wouldn't help: MySQL counts
time without queries, not time without packets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review found the idleTimeout change did nothing: mysql2 runs its idle
check only while maxIdle is below connectionLimit, and both default to
10. Turning the check on keeps a timer alive, so every test and bin that
imports the DB without db.end() hangs.

Instead, retry once when a query fails with errno 4031. The server closed
the connection for idling before the query arrived, so the query never
ran, and the pool discards that connection on the error: the retry gets
a fresh one, writes included. Nothing else is retried; a connection lost
mid-query may have run its write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jarstelfox jarstelfox changed the title Drop idle DB connections before MySQL does Retry a query once when MySQL closed it for idling Oct 20, 2026
mysql2 ignores idleTimeout while maxIdle equals connectionLimit, so the
setting and its comment described something that didn't happen. The
retry on errno 4031 is the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jarstelfox
jarstelfox merged commit 2eb6401 into master Oct 20, 2026
1 check passed
@jarstelfox
jarstelfox deleted the db-idle-timeout branch October 20, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant