Skip to content

fix(query): send Sync after an error in a rows-limited query#3708

Open
GiHoon1123 wants to merge 1 commit into
brianc:masterfrom
GiHoon1123:fix-issue-3707-rows-error-sync
Open

fix(query): send Sync after an error in a rows-limited query#3708
GiHoon1123 wants to merge 1 commit into
brianc:masterfrom
GiHoon1123:fix-issue-3707-rows-error-sync

Conversation

@GiHoon1123

Copy link
Copy Markdown

Fixes #3707.

Problem

Running a rows-limited query (client.query({ text, rows: N }), which paginates results via repeated Execute+Flush) permanently wedges the connection if a Postgres error occurs partway through fetching. The erroring query itself rejects correctly, but every subsequent query on that same client hangs forever with no error and no timeout.

Root cause

rows-limited queries use the extended query protocol's Execute+Flush cycle to fetch pages of results. If Postgres reports an ErrorResponse mid-fetch, it then waits for a Sync message before it will send ReadyForQuery. Query#handleError() never sent Sync for the rows case (only handleCommandComplete()/handleEmptyQuery() did, on the success path) — so after an error, the connection was left with readyForQuery permanently false, and the next query queued internally forever.

Fix

  • Extracted the existing if (this.rows) { connection.sync() } call sites (handleCommandComplete, handleEmptyQuery) into a shared _syncRows(connection) helper.
  • Call _syncRows(connection) from handleError() too, so the error path now sends Sync just like the success path already did.
  • Added a _hasSentSync flag so _syncRows is a no-op if a Sync was already sent for this query. This matters for one existing path: prepare()'s bind-throw handler already sent its own connection.sync() before calling handleError() — without the flag, that would now send a duplicate Sync.

Testing

  • New integration test (test/integration/gh-issues/3707-tests.js): runs a rows: 2 query that hits a real division-by-zero error partway through (generate_series with a 10 / (5 - i) expression), then races a follow-up select 1 against a 4s timer. Verified this test fails with 'follow-up hung' (not a CI-hanging timeout - it fails fast within the 4s window) against the code before this fix, and passes after.
  • New unit test (test/unit/client/throw-in-bind-tests.js): asserts Sync is sent exactly once (not twice) when a bind throws for a rows-limited query, covering the _hasSentSync de-duplication path specifically.
  • Ran the full test-unit suite (271 assertions) and the test-integration suite locally against a real Postgres instance - both clean aside from two pre-existing failures in my local environment unrelated to this change (missing SSL cert setup for 2085-tests.js, missing psql CLI for 130-tests.js).

Error during a rows-limited query permanently wedges the connection.

Query#handleError() never sent Sync for queries using the `rows`
option (paged Execute+Flush fetching). PostgreSQL waits for Sync
before sending ReadyForQuery, so once an error occurred mid-fetch, the
connection was left with readyForQuery permanently false and every
subsequent query on that client queued forever with no error and no
timeout.

Route the existing rows-only sync call sites through a shared
_syncRows() helper and call it from handleError() too. A _hasSentSync
flag prevents a duplicate Sync in the one path that already sent its
own (the bind-throw path in prepare()), which already called
connection.sync() directly before invoking handleError().

Fixes: brianc#3707
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant