Skip to content

fix(timeout): server-side query_timeout on MySQL and one client-side fallback for PostgreSQL, MySQL, MariaDB - #465

Merged
tianzhou merged 2 commits into
mainfrom
fix/query-timeout-parity
Oct 9, 2026
Merged

tianzhou merged 2 commits into
mainfrom
fix/query-timeout-parity

Conversation

@tianzhou

@tianzhou tianzhou commented Oct 9, 2026

Copy link
Copy Markdown
Member

Follow-up to the discussion on #449.

Problem

query_timeout behaved differently on each connector:

Server-side limit Client-side fallback
PostgreSQL statement_timeout 5s after the limit, but the ROLLBACK then queued behind the abandoned statement and the stuck client went back to the pool
MySQL none mysql2 timer at the limit, then KILL QUERY (#386)
MariaDB max_statement_time none

Measured against local servers with query_timeout = 1 and the server-side limit cleared in the same batch:

  • PostgreSQL returned after 12s instead of 6s.
  • MariaDB ran the full SLEEP(30).

Changes

MySQL: server-side limit. SET SESSION max_execution_time runs on every new pool connection, so the server stops a read-only SELECT at the limit and the connection stays usable. A server that rejects the variable (MySQL before 5.7.8) is ignored; the fallback still applies.

One fallback for all three (src/utils/query-timeout.ts). It fires 5s after query_timeout, and then:

  1. skips the ROLLBACK, which would queue behind the abandoned statement;
  2. discards the connection instead of returning it to the pool;
  3. cancels the statement on the server over a separate connection: pg_cancel_backend on PostgreSQL, KILL QUERY on MySQL, and the mariadb driver's own KILL on destroy().

The connection is discarded before the cancel, so a pool at its limit (pool_max_connections = 1) does not wait on itself.

MariaDB gets its own timer (withClientQueryDeadline), because the mariadb driver has no client-side one.

Behaviour change

max_execution_time does not cover writes or DDL. Those are still bounded only by the client-side fallback, which now fires 5s after query_timeout instead of exactly at it. This is the cost of using one rule on all three connectors.

Not in this PR

Tests

  • Unit: 1193 passed (pnpm test:unit), including new cases for the shared module and each connector's fallback path.
  • Integration: five new cases across the PostgreSQL, MySQL and MariaDB suites. Docker was not available where this was written, so the Testcontainers suites did not run; CI is their first run. The five new test bodies were run unchanged against local PostgreSQL 16.15, MySQL 8.0.46 and MariaDB 10.11.14, and all passed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CQAiMsFZyDVEdGv5DXztxK


Generated by Claude Code

… client-side fallback

query_timeout behaved differently per connector. PostgreSQL and MariaDB
applied it on the server (statement_timeout, max_statement_time); MySQL
only had mysql2's client-side timer plus KILL QUERY. The client-side
fallback differed too: PostgreSQL's fired 5s after the limit but then
queued a ROLLBACK behind the abandoned statement and returned the stuck
client to the pool, and MariaDB had no fallback at all, so a statement
ran unbounded once max_statement_time was cleared on the connection.

MySQL: set max_execution_time on every new pool connection, so the
server stops a read-only SELECT at the limit and the connection stays
usable. Writes and DDL are not covered by that variable and stay
bounded by the client-side fallback.

Fallback, now the same on PostgreSQL, MySQL and MariaDB
(src/utils/query-timeout.ts): it fires 5s after query_timeout, skips the
ROLLBACK, discards the connection, and cancels the statement on the
server over a separate connection (pg_cancel_backend, KILL QUERY, or
the mariadb driver's own KILL on destroy). The connection is discarded
before the cancel so a pool at its limit cannot wait on itself.

Behaviour change: a MySQL statement the server does not stop (a write,
DDL) now times out 5s after query_timeout instead of exactly at it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQAiMsFZyDVEdGv5DXztxK
Copilot AI balanced review requested due to automatic review settings October 9, 2026 08:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Large or non-finite timeout values can be clamped to an immediate Node timer, causing incorrect query cancellation.

2 open findings
What changed in this PR

Unifies query-timeout fallback behavior across PostgreSQL, MySQL, and MariaDB while adding MySQL server-side enforcement.

Changes:

  • Adds shared client-side timeout and cancellation policy.
  • Discards timed-out connections and attempts server-side cancellation.
  • Adds unit, integration, and configuration documentation coverage.
File Description
src/​utils/​readonly-transaction.ts Uses shared timeout detection.
src/​utils/​query-timeout.ts Adds shared timeout utilities.
src/​utils/​__tests__/​query-timeout.test.ts Tests timeout utilities.
src/​types/​config.ts Clarifies timeout behavior.
src/​connectors/​postgres/​index.ts Discards clients and cancels backends.
src/​connectors/​mysql/​index.ts Adds server limits and unified fallback.
src/​connectors/​mariadb/​index.ts Adds client-side fallback.
src/​connectors/​__tests__/​shared/​query-timeout-helpers.ts Adds integration-test helpers.
src/​connectors/​__tests__/​readonly-transaction-strategy.test.ts Tests MySQL/MariaDB timeout paths.
src/​connectors/​__tests__/​postgres.integration.test.ts Tests PostgreSQL fallback.
src/​connectors/​__tests__/​postgres-query-timeout.test.ts Tests PostgreSQL cancellation ordering.
src/​connectors/​__tests__/​mysql.integration.test.ts Tests MySQL timeout enforcement.
src/​connectors/​__tests__/​mariadb.integration.test.ts Tests MariaDB timeout enforcement.
docs/​config/​toml.mdx Documents server and fallback behavior.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/utils/query-timeout.ts Outdated
Comment thread docs/config/toml.mdx Outdated
…cellation as best-effort

Node's setTimeout clamps delays above 2^31-1 ms, NaN and Infinity to 1 ms,
and TOML accepts `inf`, so a large or non-finite query_timeout turned the
client-side fallback into an immediate timeout on every statement. The
config loader now rejects such values (at most 2147478 seconds), and
clientQueryTimeoutMs throws on the direct ConnectorConfig path instead of
scheduling a deadline Node cannot represent.

The docs said DBHub cancels the statement after the fallback fires; the
connectors only attempt it, so say so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

3 participants