Skip to content

fix: settle COPY writable final callback on server error - #1183

Open
GiHoon1123 wants to merge 1 commit into
porsager:masterfrom
GiHoon1123:fix-1173-copy-error-writable-hang
Open

fix: settle COPY writable final callback on server error#1183
GiHoon1123 wants to merge 1 commit into
porsager:masterfrom
GiHoon1123:fix-1173-copy-error-writable-hang

Conversation

@GiHoon1123

Copy link
Copy Markdown

Fixes #1173

Problem

When COPY ... FROM STDIN fails after .end() has sent CopyDone, the
pending writable final callback is never called.

On the success path, CommandComplete calls the stored final callback.
On the failure path, Postgres sends ErrorResponse and ReadyForQuery
instead, which goes through errored(). That path cleaned up the current
stream/query state, but did not settle the pending final callback.

That leaves stream.pipeline() or any code awaiting the writable stuck
forever. Inside sql.begin(), the transaction callback never returns, so
ROLLBACK is not sent and the connection is left idle in an aborted
transaction.

Fix

Call the pending final callback with the Postgres error from errored():

final && (final(err), final = null)

This makes the writable reject with the server error, allowing callers to
handle it normally and allowing sql.begin() to roll back.

The existing mid-write error path is unchanged: if the error arrives before
.end() stores final, stream.destroy(err) still handles it.

Testing

  • Added a regression test for a failed COPY FROM STDIN after .end()
  • Verified the writable rejects with the real Postgres error
  • Verified no connection is left idle in transaction (aborted)

Failed COPY FROM STDIN never invoked the stashed Writable/Duplex
final() callback when the server rejected the data with ErrorResponse
instead of CommandComplete. This left the writable hanging forever,
which meant stream.pipeline() (and any await on stream finish) never
settled. Inside sql.begin(), the transaction callback never returned,
so ROLLBACK was never sent and the connection leaked permanently as
'idle in transaction (aborted)'.

errored() already cleaned up stream/query/initial on this path but
never invoked the pending final callback. Calling final(err) settles
the stream with the real PostgresError, so pipeline() rejects,
sql.begin() can roll back, and the connection returns to the pool.

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

Labels

None yet

Projects

None yet

1 participant