feat(pglite-socket): handle CancelRequest wire protocol message#900
Conversation
|
Hey @tdrz @samwillis 👋 Would love your eyes on this when you get a chance. We ran into this while building a unified local dev mode for our platform since we use PGLite via pglite-socket as a zero-config Postgres backend, connected through Cloudflare Hyperdrive (Miniflare's local proxy) for production-parity connection handling. The issue is that Hyperdrive sends an SSLRequest message during connection setup, and pglite-socket doesn't handle it; the 8-byte SSLRequest falls through to the typed message parser, corrupts the protocol buffer, and crashes the connection. CancelRequest has the same issue. The fix handles both SSLRequest and CancelRequest as untyped startup-phase messages (responding with 'N' for SSL, silently acking cancel), and adds a startupComplete flag to prevent regular typed messages from being misinterpreted during the handshake phase. This is a pretty common pattern for Postgres proxies (pgbouncer, Hyperdrive, etc.), so should help anyone trying to put a connection pooler in front of PGLite. All existing tests pass, and I've added 3 new ones for the wire protocol edge cases. Happy to address any feedback, thanks! |
|
@tdrz just following up 🫡 |
|
@bookernath Thank you for this! While I do understand the motivation I am not comfortable with the proposed implementation. PGlite wraps Postgres, which already handles all these cases. Why can't we rely on it to handle them? |
|
Thanks, fair question. I actually went and tried this before replying. Sent raw It doesn't. Both come back with: That's an The reason is structural, as far as I can tell. In real Postgres, SSL negotiation and cancel handling live in the postmaster ( That's what pushed me to put this in That said, if you'd rather the fix live in PGlite itself (e.g. have Other shape changes I'm happy to make if it lands here:
|
|
@bookernath I've merged a different PR that was replying 'N' to SSL requests. Please rebase your work and adapt the cancel request response in a way that keeps things understandable (no magic numbers spread through the code, extract logic to a separate function). |
99dc20b to
7977a93
Compare
|
@tdrz ♻️ |
|
@bookernath Thank you for this! |
tdrz
left a comment
There was a problem hiding this comment.
Just a small change, the rest looks good! Thank you!
Rebased onto main, which now handles SSLRequest (replies 'N') via the extracted handleSslRequest() method (electric-sql#990). Adapts the remaining CancelRequest handling to that same style as requested in review: adds named CANCEL_REQUEST_CODE / CANCEL_REQUEST_LENGTH constants (no magic numbers) and extracts the logic into a dedicated handleCancelRequest() method called from the handleData() loop alongside handleSslRequest(). PGlite has no backend process to signal, so the request is consumed and silently ignored (the protocol expects no response). Drops the now-redundant inline SSL handling and SSL-specific tests (covered by upstream's ssl-request.test.ts); keeps a CancelRequest integration test.
7977a93 to
4d8033a
Compare
|
@tdrz great point, I changed to |
Summary
Adds handling for the PostgreSQL CancelRequest wire protocol message to
pglite-socket.80877102): consume the 16-byte message and silently ignore it. PGLite has no backend process to signal, and the wire protocol expects no response.Motivation
CancelRequest, SSLRequest, and StartupMessage are all "untyped" startup-phase messages in the PostgreSQL wire protocol — no type-byte prefix, just
[length (int32 BE)][code (int32 BE)][...]. Connection proxies (e.g. Cloudflare Hyperdrive) and standard clients can open a separate connection that begins with a CancelRequest.Without explicit handling, a CancelRequest falls through to the typed-message parser, which reads byte 0 as a message type and bytes 1–4 as a length. For the 16-byte CancelRequest (
00 00 00 10 04 D2 16 2E ...) this yields a bogus oversized length, so the message is buffered indefinitely and the connection stalls / the stream is corrupted. Consuming it explicitly during startup keeps the parser in a sane state.Changes
packages/pglite-socket/src/index.tsCANCEL_REQUEST_CODE/CANCEL_REQUEST_LENGTHconstants and an extractedhandleCancelRequest()method, called from thehandleData()loop alongside the existinghandleSslRequest()packages/pglite-socket/tests/query-with-node-pg.test.tsThe implementation mirrors
handleSslRequest()from #990 — no magic numbers in the loop, logic extracted into a dedicated method — as requested in review.Tests
pg.Clientconnection afterward.(SSLRequest is now covered by
tests/ssl-request.test.tsfrom #990, so the SSLRequest-specific tests from the original version of this PR were removed.)Testing done
pnpm build— full build passes (ESM + CJS + DTS)pnpm test— 61/61 pass