Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Error during a `rows`-limited query permanently wedges the connection — `handleError` sends no Sync

Aperta
#3,707 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
55/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Tranquilla
Stack tecnologico
javascript, postgresql
Ambito
backend, databases

Direzione di ricerca

Leggi lib/query.js, in particolare Query._getRows(), handleError(), handleCommandComplete() e handleEmptyQuery(); confronta con packages/pg-cursor/index.js. Esegui la riproduzione fornita in modalità rows e aggiungi una copertura di regressione che verifichi che un ErrorResponse sia seguito da un ReadyForQuery e che una query successiva venga risolta, includendo il possibile double-sync edge per la revisione del maintainer.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug
  • pg: 8.22.0
  • Node: 24.14.1
  • PostgreSQL: 18.4 (macOS; not server-specific — the wedge follows from the wire protocol)

Summary

When a query runs with the rows option (portal suspension — client.query({ text, rows: N })),
an ErrorResponse from the server permanently wedges the connection: the query itself rejects
correctly, but ReadyForQuery never arrives, client.readyForQuery stays false, and every
subsequent query on that client queues forever.

Found while developing a wire-protocol bridge, where the message flow made the missing
frame visible.

Reproduction

const { Client } = require('pg')

async function main() {
  const client = new Client()
  await client.connect()

  // Errors mid-portal: division by zero on row 5, fetched in pages of 2.
  await client
    .query({ text: 'select 10 / (5 - i) as v from generate_series(1, 7) g(i)', rows: 2 })
    .catch((err) => console.log('query rejected:', err.message)) // "division by zero" — fine

  // The connection is now wedged: this never settles.
  const result = await Promise.race([
    client.query('select 1').then(() => 'follow-up resolved'),
    new Promise((r) => setTimeout(() => r('follow-up HUNG'), 5000)),
  ])
  console.log(result) // "follow-up HUNG"
}
main()

Root cause

With rows set, Query.prepare() drives the extended protocol with Flush, not Sync:
_getRows() sends Execute(rows=N) + connection.flush(), and again on every
PortalSuspended; Sync is only sent from handleCommandComplete / handleEmptyQuery
(lib/query.js). When the server answers with ErrorResponse instead, it enters
ignore-till-sync — it discards everything until a Sync arrives and withholds
ReadyForQuery. handleError sends nothing, so the RFQ never comes and the client's
queue is stuck permanently.

Some history, because the code carries a fossil of the old behavior: handleError did
send Sync from at least 2014 (// need to sync after error during a prepared statement
— the comment is still in today's source). The Oct 2020 pipelining redesign (d31486fb,
then dd3ce616) moved Sync into _getRows(), pipelined right after Execute, and removed
the handleError call — correct for the normal prepared path, where a Sync is already
in flight by the time an error arrives. But the rows branch of _getRows() pipelines
Flush instead, so rows-mode was left with no error-path Sync at all; only the comment
survived.

pg-cursor — which uses the same Execute+Flush model — deliberately kept its
handleError sync for exactly this reason (packages/pg-cursor/index.js:
// call sync to trigger a readyForQuery), which is why cursors recover from errors
and the core rows path does not.

Suggested fix

Mirror what handleCommandComplete / handleEmptyQuery already do for rows-mode:

handleError(err, connection) {
  // rows-mode pipelines Flush, not Sync (_getRows) — after an ErrorResponse the
  // backend discards messages until Sync, so send it here or ReadyForQuery never
  // arrives and the connection is wedged.
  if (this.rows) {
    connection.sync()
  }
  // ... existing body unchanged
}

One edge worth a maintainer's judgement: 9c678e10 (Oct 2020) guarded the old
sync-after-error because PostgreSQL 9.x could send both CommandComplete and
ErrorResponse for a single timed-out query. If that pairing is still in scope,
handleCommandComplete would already have sent Sync and the line above would
double-send (two RFQs — the class of bug #2420 fixed in pg-cursor). A small
this._syncSent flag shared by the three handlers would make it airtight.

I searched for an existing report and found none covering this path — the closest are
#549 (a crash in the pre-2020 handleError sync call) and #1500 (readyForQuery stuck
after a dead connection). Since rows is undocumented, this is a low-visibility
surface — but the wedge is silent and permanent when hit.

Lingua principale
JavaScript
Stelle
13.2k
Fork
1.4k
Merge medio
6g 15h
PR unite (30g)
6

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di brianc/node-postgres

Tutte le issue di brianc/node-postgres

Issue simili

Altre issue su JavaScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.