Skip to content

[miniflare] Hyperdrive proxy crashes the Node process when the client socket errors #15768

Description

@Pduhard

What versions & operating system are you using?

  System:
    OS: Linux 7.0 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
    CPU: (32) x64 AMD Ryzen 9 9950X 16-Core Processor
    Memory: 16.45 GB / 30.45 GB
    Container: Yes
    Shell: 5.9 - /usr/bin/zsh
  Binaries:
    Node: 22.14.0
    npm: 10.9.2
    pnpm: 11.24.0
  npmPackages:
    miniflare: 4.20260730.0 => 4.20260730.0

Please provide a link to a minimal reproduction

https://github.com/Pduhard/miniflare-hyperdrive-crash-repro

Describe the Bug

The local Hyperdrive proxy pipes the workerd client socket to the database socket. Only the database side gets an error listener.

In packages/miniflare/src/plugins/hyperdrive/hyperdrive-proxy.ts on main:

  • createPlainTCPConnection attaches dbSocket.on("error", ...)
  • setupTLSConnection attaches tlsSocket.on("error", ...)
  • clientSocket gets nothing, at any of the four places it is piped: #handleConnection, handlePostgresTlsConnection, handleMySQLTlsConnection, setupTLSConnection. It also gets nothing before those pipes exist: on the TLS paths, #handleConnection writes to the database, reads the reply and runs a TLS handshake first, and a client that resets during that window crashes the process the same way

pipe() does not cover this. It attaches an onerror handler to the destination, but that handler removes itself and re-emits once no other listener is left. So when the client socket errors after the pipes are set up, nothing is listening, and the whole Node process exits. That takes down wrangler dev, getPlatformProxy(), or a vitest run.

Steps to reproduce

git clone https://github.com/Pduhard/miniflare-hyperdrive-crash-repro
cd miniflare-hyperdrive-crash-repro
npm install
npm run repro:half-close
npm run repro:reset

repro:half-close runs a TCP server that speaks just enough of the Postgres protocol to negotiate TLS, then streams data. A Worker connects through a Hyperdrive binding and half-closes its socket while that data is still arriving. Nothing reaches into Miniflare internals.

repro:reset covers the other window: it talks to the proxy port directly and resets the connection while the proxy is still negotiating TLS with the database, before any pipe exists.

Expected: the connection is torn down and Miniflare keeps running.
Observed: the process exits 1 on an uncaught error.

Which configurations reach this code

Since #13390 the proxy is skipped when sslmode=disable, so that mode no longer reaches it. require, prefer, verify-ca and verify-full still do, which is the ordinary setup for developing locally against a hosted Postgres that requires TLS.

version sslmode=disable sslmode=require
4.20260424.0 exits 1 exits 1
4.20260730.0 20 of 20 attempts survive exits 1

The same code is on main today.

Please provide any relevant error logs

node:events:496
      throw er; // Unhandled 'error' event
      ^

Error: write EPIPE
    at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:87:19)
Emitted 'error' event on Socket instance at:
    at Socket.onerror (node:internal/streams/readable:1028:14)
    at Socket.emit (node:events:518:28)
    at emitErrorNT (node:internal/streams/destroy:170:8)
    at emitErrorCloseNT (node:internal/streams/destroy:129:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}

Socket.onerror at readable.js:1028 is the listener pipe() installed on the destination, re-emitting because nothing else was listening. Depending on whether the write lands just before or just after the peer's FIN, the same failure also shows up as Error: This socket has been ended by the other party at Socket.writeAfterFIN.


PR: #15769. It attaches the listener as soon as the connection is accepted and at each pipe site, with a regression test for each of the two windows in packages/miniflare/test/plugins/hyperdrive/proxy.spec.ts. Both tests fail on main.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    package:miniflareRelating to Miniflareproduct:hyperdriveRelating to Cloudflare Hyperdrive: https://developers.cloudflare.com/hyperdrive/

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions