Error during a `rows`-limited query permanently wedges the connection — `handleError` sends no Sync
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
- 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
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de brianc/node-postgres
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
brianc/node-postgres#3770 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
brianc/node-postgres#3716 · 1 comentario ·
-
# remove bring your own promise Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
brianc/node-postgres#3631 · 1 comentario ·
-
Dificultad 1/5 1-3 horas Aptitud para principiantes 62/100
brianc/node-postgres#2857 ·
-
can't view docs for old versions Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 68/100
brianc/node-postgres#2433 ·
Todos los issues de brianc/node-postgres
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
mksglu/context-mode#1200 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
neondatabase/website#5944 ·
-
module: core
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
bigbluebutton/bigbluebutton#25849 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
jaegertracing/jaeger-ui#4506 ·