Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

オープン
#3,707 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
55/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
静か
技術スタック
javascript, postgresql
領域
backend, databases

調査の方向性

lib/query.js、特に Query._getRows()、handleError()、handleCommandComplete()、handleEmptyQuery() を読み、packages/pg-cursor/index.js と比較してください。提供された rows モードの再現を実行し、ErrorResponse の後に ReadyForQuery が続き、その後のクエリが解決されることを検証するリグレッションカバレッジを追加してください。maintainer によるレビュー用に、double-sync edge の可能性も含めてください。

索引モデルが issue の本文から書いたものです。

説明

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.

主要言語
JavaScript
スター
13.2k
フォーク
1.4k
平均マージ
6日 15時間
マージ済み PR(30日)
6

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

brianc/node-postgres のほかの issue

brianc/node-postgres の issue をすべて見る

似ている issue

JavaScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。