Skip to content

fix(pg-cursor): settle close() after the cursor has errored - #3796

Open
teoarjun wants to merge 1 commit into
brianc:masterfrom
teoarjun:fix/cursor-close-after-error
Open

teoarjun wants to merge 1 commit into
brianc:masterfrom
teoarjun:fix/cursor-close-after-error

Conversation

@teoarjun

Copy link
Copy Markdown

Fixes #2642

Cursor#close() only returns early when the cursor is done. Once handleError has moved it to error, close() sends a Close for the portal and waits for readyForQuery. No Sync follows that Close (handleError already sent one), so the wait only ends if the error's own readyForQuery has not been handled yet. When it has, close() never settles.

That covers the reported case and a more common one:

  • the connection is lost mid-read (pg_terminate_backend, a failover): the socket is dead, so nothing arrives;
  • an ordinary query error followed by close(), for example in a finally block after the rejected read().

In the error state the portal no longer exists on the server (it is dropped at the Sync that handleError sends, or with the connection), so there is nothing to close. close() now settles straight away, as it does for done.

Tests: two cases in packages/pg-cursor/test/close.js (close after a query error once the client has drained; close after the backend is terminated). Both time out without the change and pass with it. pg-cursor (45) and pg-query-stream (40) pass against Postgres 16, and eslint and prettier are clean.

#2806 added a failing test for this issue in 2022; this PR includes a fix alongside its own tests.

close() only short-circuited when the cursor was 'done'. Once handleError
has put it in the 'error' state, close() sent a Close for the portal and
waited for a readyForQuery, but no Sync follows that Close (handleError
already sent one), so the wait could only end if the error's own
readyForQuery had not arrived yet. When it had, close() never settled.

That covers both reported cases:
- the connection is lost mid-read (pg_terminate_backend, a failover):
  the socket is dead, so nothing will ever arrive;
- an ordinary query error followed by close(), e.g. in a finally block:
  the error's readyForQuery has already been handled.

In the 'error' state the portal no longer exists on the server (it is
dropped at the Sync handleError sends, or with the connection), so there
is nothing to close. Settle straight away, as for 'done'.

Fixes brianc#2642
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.

Awaiting on pgcursor.close may never resolve

1 participant