Skip to content

Terminate backend when query_timeout is reached #1954

Open

Description

@gajus

The current implementation simply leaves the backend to hang.

node-postgres/lib/client.js

Lines 480 to 509 in 60d8df6

if (readTimeout) {
queryCallback = query.callback
readTimeoutTimer = setTimeout(() => {
var error = new Error('Query read timeout')
process.nextTick(() => {
query.handleError(error, this.connection)
})
queryCallback(error)
// we already returned an error,
// just do nothing if query completes
query.callback = () => {}
// Remove from queue
var index = this.queryQueue.indexOf(query)
if (index > -1) {
this.queryQueue.splice(index, 1)
}
this._pulseQueryQueue()
}, readTimeout)
query.callback = (err, res) => {
clearTimeout(readTimeoutTimer)
queryCallback(err, res)
}
}

This seems dangerous. A better solution would seem to be to issue pg_cancel_backend when client timeout is reached.

Activity

  1. rinarakaki commented on Oct 15, 2026

    @rinarakaki

    A consequence worth adding: after query_timeout fails a query that was already sent, the client stays busy with it. So the next query waits behind it, and fails with the same timeout without ever running. Reproduced on pg 8.23.1, the latest, against Postgres 18; master (0980cef) is the same:

    const { Client } = require("pg");
    
    const client = new Client({ connectionString: process.env.DATABASE_URL, query_timeout: 1000 });
    await client.connect();
    const start = Date.now();
    await client.query("select pg_sleep(3)").catch((error) => console.log(error.message, Date.now() - start)); // Query read timeout 1004
    await client.query("select 1").catch((error) => console.log(error.message, Date.now() - start)); // Query read timeout 2008

    Only after the server finishes the first query does the client answer again. So a ROLLBACK sent after a timed-out statement in a transaction waits out its own timeout too, about twice the timeout in all.

    In pipeline mode, a timed-out query that was sent destroys the connection for this reason (else if (this.pipeline) in client.js). Without pipelining it doesn't, and the client stays tied to the running query. We work around it by discarding the client once a query times out, sending nothing after it (client.release(error)), so the server ends the session.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions