Repository navigation
fix(timeout): server-side query_timeout on MySQL and one client-side fallback for PostgreSQL, MySQL, MariaDB - #465
Merged
Merged
Conversation
… 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
Contributor
There was a problem hiding this comment.
🟡 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.
…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>
This was referenced Oct 9, 2026
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.


Follow-up to the discussion on #449.
Problem
query_timeoutbehaved differently on each connector:statement_timeoutROLLBACKthen queued behind the abandoned statement and the stuck client went back to the poolKILL QUERY(#386)max_statement_timeMeasured against local servers with
query_timeout = 1and the server-side limit cleared in the same batch:SLEEP(30).Changes
MySQL: server-side limit.
SET SESSION max_execution_timeruns on every new pool connection, so the server stops a read-onlySELECTat 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 afterquery_timeout, and then:ROLLBACK, which would queue behind the abandoned statement;pg_cancel_backendon PostgreSQL,KILL QUERYon MySQL, and the mariadb driver's ownKILLondestroy().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_timedoes not cover writes or DDL. Those are still bounded only by the client-side fallback, which now fires 5s afterquery_timeoutinstead of exactly at it. This is the cost of using one rule on all three connectors.Not in this PR
query_timeoutis set and the server is not MariaDB 10.1.2+ (amariadb://DSN pointed at MySQL or TiDB).Tests
pnpm test:unit), including new cases for the shared module and each connector's fallback path.🤖 Generated with Claude Code
https://claude.ai/code/session_01CQAiMsFZyDVEdGv5DXztxK
Generated by Claude Code