Repository navigation
Conversation
…read() Track the handle reading state that pause(), read(), _read() and resume() set, like node's handle.reading. The onread delivery loop and the tail drain stop on that state instead of on isPaused(), which read() does not clear. Fixes #42418
…pped socket The constructor skips read(0) while the wrapped socket connects. Start the reads when the TLS handle opens, like node's constructor does for a socket built over a handle.
|
Warning Review limit reached
On-demand reviews are free for the next 8 days. After that, they cost $0.25 per reviewed file. Or wait 1 minute for your next included review. View limit detailsLimit details: You’ve used all 10 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (1)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review. WalkthroughThe socket implementation now tracks explicit onread reading state. Native reads, queued tails, pause, resume, and wrapped-handle setup use shared helpers. Regression tests cover net callback ordering and TLS upgrades during connection. ChangesSocket onread state control
Suggested reviewers: Priority: ⚪ Not assessed Merge Risk: ⚪ Minimal · up to The change has regression coverage for the updated socket and TLS read behavior, with no unresolved actionable risk identified. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Updated 2:20 AM PT - Sep 12th, 2026
✅ @robobun, your commit 44fb29931defa514bb9e4c840b55c20e6d9079f4 passed in 🧪 To try this PR locally: bunx bun-pr 42444That installs a local version of the PR into your bun-42444 --bun |
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline finding, I traced kOnreadReading initialization: every connect path (afterConnect at net.ts:3446, TLS handshake at :1044, server accept at :1294, and the new wrapped-open at :1324) reaches read(0) → readStart before the first deliver call, so switching the stop condition from isPaused() to !self[kOnreadReading] doesn't halt on the very first slice. Also confirmed no stale kOnreadReadRequested references remain, and the resume() early return ret is safe because readStart already calls restorePausedHold internally.
Extended reasoning...
The inline finding covers the test-wiring gap. Beyond it, the main risk in this diff is that kOnreadReading starts life as undefined, and the delivery loop now stops on !self[kOnreadReading] where it previously stopped on self.isPaused() — so if any path can reach deliver() before something sets the flag, the first native chunk would be truncated to one slice. I walked every read(0) site that fronts a data-producing handle (client afterConnect, TLS secureConnect, server-side accept, the constructor's nextTick for already-open handles, and the newly added wrapped-socket open) and each reaches readStart (or drainOnreadTail, which also sets the flag) before data can arrive. The resume() refactor's new early return looked like it might skip the trailing restorePausedHold, but readStart calls it itself, so the hold is still restored on that branch. The TLS test's dual onread + 'data' accumulator was also examined: it can't distinguish which path delivered, but the regression it guards (reads never restart, so end never fires) fails regardless of which listener would have received the bytes.
…n-pause onread test
|
Addressed the test wiring nit in 263cca7: the read-then-pause test now rejects both awaited promises on the client's error and close events. |
|
Shortened the three flagged comments in net.ts to one line each (44fb299). |
|
Replied to the nit on the TLS test: the data listener stays. Node emits data (not onread) for a TLS socket over a wrapped socket, and #42425 aligns bun with that. The test asserts that the reads run to the FIN, whichever path delivers the bytes. |
|
This change also fixes a wider group of stalls that have the same cause. I worked on that group and did not open a PR, because this PR covers it. The notes below are for this PR to use. The other symptoms An
Node v26.3.0 and bun 1.3.14 deliver every byte in each case. So this is a regression from #32630 (cc40d78), first released in v1.4.0. It is the same over TLS and over a unix socket. What I checked against this PR I applied the Tests you can take Commit a181e85 on
All 25 fail on bun 1.4.3 (24 time out on the stall) and pass with the change of this PR. The tests of this PR do not cover a The second commit on that branch (d67e4f6) is my version of the fix, for reference. It keeps the flag narrow: only the Landing order with #42313 #42313 is approved and changes the same loop, so it will probably land first. Two things to know for the rebase:
|
|
I also worked on the A 218-cell comparison with node v26.3.0, run on the head of this PR (44fb299) A cell is: tcp or tls, the bytes in one burst or one slice per read, one of 11 consumers of the read side (
The notes in the PR body say that node starts no reads at all until The cause, and a small change that fixes the 4 cells
In my version the flag starts as The table as a test, if you want it Commit 59a2c36 on the same branch adds |
Problem
onreadcallback that callssocket.pause()and thensocket.read()gets no more calls. The rest of the chunk stays inkOnreadTailuntil a laterresume(). Node delivers it: the script in node:net: pause() then read() inside an onread callback stops the delivery #42418 prints[ 'abcd', 'efgh' ]in node and[ "abcd", "no call for 1 s" ]in bun.deliverclosure of theSocketconstructor (src/js/node/net.ts):ret === false || self.isPaused().Readable.prototype.read()does not clear the paused flag of the stream, soisPaused()is stilltrueafterpause(); read().Fix
kOnreadReading, the equivalent of node'shandle.reading.readStop()clears it. The newreadStart()helper (thehandle.resume()plusrestorePausedHold()pair thatread(),_read(),resume()and the tail drain all had inline) anddrainOnreadTail()set it.deliveranddrainOnreadTailNTstop on that flag. This replaces thefromReadandkOnreadReadRequestedplumbing, which only covered aread()that ran after the tail existed.pause()stops the handle,read()andresume()start it, andonStreamReadstops it only on afalsereturn (net.js#L817-L845, stream_base_commons.js#L191-L198).net.Socketthat is still connecting never calledread(0), so its flag stayed unset. The TLS handle open now callsread(0)unless the socket is paused, like node's constructor for a socket built over a handle.test/js/node/net/node-net.test.ts(two newonreadtests, the first fails on the released bun) andtest/js/node/tls/node-tls-upgrade.test.ts(one new test). Also ran the othernet,tlsandhttpsuites and node'stest-net-*andtest-tls-*tests that wrap a socket.Background
onreadsocket hands each read to a user callback with a user buffer instead ofpush(). Node bounds each kernel read to that buffer. Bun reads bigger native chunks and slices them indeliver, so a stop in the middle of a chunk stores the rest inkOnreadTailfor a laterdrainOnreadTailNTtick.readStop()pauses the native handle and drops its hold on the event loop.restorePausedHold()gives the hold back when reads start again.kOnreadReadRequestedindropOnreadTail(). Whichever PR lands second drops that line.Notes
pause(); read(),read(); pause(),pause(); resume(),falsethenread(), FIN together with data,pause(); read()on the last slice with a FIN,resume(); pause()inside the callback, a late FIN thenresume(). All match.resume()), andread(); pause()inside a TLS callback stops at once where node's TLSWrap still emits the bytes it already decrypted.pause()on a socket that is not reading still callsreadStop(), as before.readStop()is idempotent.net.Socket readgroup (ECONNREFUSED on alocalhostlistener),unref should exit(DNS), theconnect({path})leak test (times out under ASAN),SNICallback runs even when..., and threenode-http-connectandnode-http-proxy-urltests that time out under ASAN.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/node/net/node-net.test.ts