Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

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

Abierto
#3,707 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
55/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Tranquilo
Stack tecnológico
javascript, postgresql

Línea de trabajo

Lee lib/query.js, especialmente Query._getRows(), handleError(), handleCommandComplete() y handleEmptyQuery(); compara packages/pg-cursor/index.js. Ejecuta la reproducción proporcionada en modo rows y añade cobertura de regresión que verifique que a un ErrorResponse le sigue un ReadyForQuery y que una consulta posterior se resuelve, incluyendo el posible double-sync edge para la revisión del maintainer.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
JavaScript
Estrellas
13.2k
Forks
1.4k
Merge medio
6 d 15 h
PR fusionados (30 d)
6

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de brianc/node-postgres

Todos los issues de brianc/node-postgres

Issues similares

Más issues de JavaScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.