The bug
openTunnel finishes a CONNECT (or SOCKS5) handshake holding a buffered reader — the peer may have
sent tunnel payload in the same chunk as the reply — and handed that onward as
new ReadableStream({ pull }). Correct, and a plain stream rather than a byte stream.
The TLS record layer asks for a BYOB reader and silently falls back to a default one when it cannot
have it. So every proxied connection lost BYOB reads, and with them tls.pullBytes, whose only
job is to size them.
Measured on the edge, 1 MB through the record layer, n=15 in one isolate:
pullBytes: 16 KiB |
pullBytes: 1 MiB |
|
|---|---|---|
| direct | min 25, p50 31 | min 73, p50 90 |
| proxied | min 95, p50 111 | min 101, p50 121 |
2.9× on a direct socket and 6% through a proxy — the knob was not being read.
The fix
src/proxy/tunnel.js makes the tunnel a byte stream and, once the handshake's leftovers are
drained, hands the caller's own view straight to the socket. Both dialects share it. A transport
whose readable is not a byte stream keeps the previous buffered path.
The default moved: 64 KiB → 16 KiB
The old value came from a sweep captioned "against a real proxied socket" whose knob was never
reaching the code on that path — four samples of one configuration. Re-swept with it connected,
ms for 1 MB at the record layer, p50:
| 8 KiB | 16 KiB | 32 KiB | 64 KiB | 256 KiB | |
|---|---|---|---|---|---|
| direct | 21 | 20 | 18 | 22 | 40 |
| proxy A | 51 | 61 | 61 | 89 | 152 |
| proxy B | 68 | 74 | — | 102 | — |
A BYOB read resolves the instant any byte exists and never waits to fill, so the view is a ceiling
rather than a target — average fill over 4 MB is 37 KB direct and 8 KB through a proxy, and
everything above that is allocation that is never used.
End to end through the Client and a proxy, n=13: 63 against 78 ms for 1 MB and 145 against
164 ms for 4 MB on the median.
No API changed
tls.pullBytes still overrides the default. If you had tuned it for a proxied connection, it was
not being read before this release.
Also
- The cost tables are re-measured against a rebuilt, now-committed
sizeorigin/, with the method
stated: a warm page is(reuse=4 - reuse=1)/3. The 4 MB row reproduced; the mid sizes came in
20–30% lower for reasons that are not this release's changes, and the README says so. - The pricing table had an arithmetic bug independent of any of this — it billed every request and
every CPU millisecond, ignoring the included allowance its own prose describes, overstating the
10M/month column by up to 74%. maxBodyBytes: Infinityis now priced on a real request rather than a fixture: it is worth
20–27 ms on a 4 MB body, roughly $540/month at a billion such requests.