Skip to content

fix(pg-cloudflare): do not emit an error for the read that end() cancels - #3806

Open
WolfieLeader wants to merge 2 commits into
brianc:masterfrom
WolfieLeader:fix/cloudflare-end-unhandled
Open

WolfieLeader wants to merge 2 commits into
brianc:masterfrom
WolfieLeader:fix/cloudflare-end-unhandled

Conversation

@WolfieLeader

Copy link
Copy Markdown

Fixes #3805.

Since #3747, CloudflareSocket.end() closes the socket while _listen() still waits on a read. workerd rejects that read with "This socket has been closed.", and pg re-emits it as a client 'error' after end() resolved. With no 'error' listener, that is an unhandled rejection.

This change ignores a rejected read once end() has been called. Other read failures still emit 'error'.

Tests:

  • New unit test in packages/pg-esm-test/pg-cloudflare.test.js. A fake reader keeps the read pending until the fake close() rejects it, as workerd does. Before: 36 pass, 1 fail. After: 37 pass.
  • workerd (Workers Vitest pool, pg 8.23.1, local Hyperdrive to PostgreSQL 18), await client.end() after a query:
    • 1.4.1: unhandled rejection in 3 of 3 runs.
    • This branch: clean in 3 of 3 runs.
    • A server-side disconnect (pg_terminate_backend) still reaches the client as 'error'.

Not changed: _listenOnce(), the SSL handshake read. In workerd, end() during the handshake did not reject that read.

Since brianc#3747, end() closes the Cloudflare socket while _listen() still
waits on a read. workerd rejects that read with "This socket has been
closed.", and pg re-emits it as a client 'error' after end() resolved.
With no 'error' listener this is an unhandled rejection.

Ignore a rejected read once end() has been called. Other read failures
still emit 'error'.

@robingenz robingenz left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested this change, and it works for me.

I built packages/pg-cloudflare from this branch (6f83843) and ran it against PostgreSQL 17 with pg 8.23.1:

  • wrangler dev (wrangler 4.142.0, workerd 1.20260926.1): client.end(), pool.end() and a pool's idle timeout no longer emit This socket has been closed. Without the change, all three do.
  • Workers Vitest pool (@cloudflare/vitest-pool-workers 0.22.0, workerd 1.20260815.1): same result. There the old error text is Stream was cancelled.
  • A real failure is still reported. pg_terminate_backend() during SELECT pg_sleep(5) fails the query with terminating connection due to administrator command, and the client emits Connection terminated unexpectedly.

One suggestion: the new test covers the read that end() cancels, but nothing checks that a read failing before end() still fails _listen(). A small test would guard the throw e branch:

it('should still fail listening when a read fails before end()', async () => {
  const socket = new CloudflareSocket()
  socket._cfReader = { read: () => Promise.reject(new Error('Network connection lost.')) }

  await assert.rejects(socket._listen(), /Network connection lost/)
})

With it, node --test --conditions=workerd pg-cloudflare.test.js in packages/pg-esm-test passes 6 of 6 on this branch. On master, your new test fails with TypeError: This socket has been closed., as expected.

The two cases from the latest comment on #3805, a read rejected after the peer already closed the socket and an emit('error') with no listener left, are not covered here. Worth a follow-up in this PR or a second one.

@WolfieLeader

Copy link
Copy Markdown
Author

Wow, thank you @robingenz Ill check that

@WolfieLeader

Copy link
Copy Markdown
Author

Thanks @robingenz for testing this on wrangler dev and the Vitest pool. I added your test as is in 572b3b4.

That commit also covers the first case from @SlambertDK's comment: _listen() now ignores a read that fails after the peer closed the socket. By then 'close' has fired, and pg reports Connection terminated unexpectedly from it.

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.

pg-cloudflare 1.4.1: client.end() emits "This socket has been closed." error

2 participants