Repository navigation
Conversation
_bytesDispatched reads this._handle.bytesWritten while a handle exists, like node. It falls back to the value that _destroy saved. bytesWritten builds on it. A writer that bypasses the JS write path, such as the node:http2 frame writer, now shows up on the raw net.Socket. The bytesWritten special cases in the http2 socket proxies are gone. The default case of each proxy now returns the same value. Fixes #42465
|
Updated 5:22 AM PT - Sep 12th, 2026
✅ @robobun, your commit 876228ace303313401b27036739a5aa518363597 passed in 🧪 To try this PR locally: bunx bun-pr 42468That installs a local version of the PR into your bun-42468 --bun |
|
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; 2 remain after this review. WalkthroughChangesThe socket byte counters now read native handle values while handles exist and cached values after teardown. HTTP/2 socket proxies use default forwarding, and regression tests verify counter consistency before and after socket closure. HTTP/2 byte accounting
Suggested reviewers: Priority: ➖ Normal Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to The updated byte-accounting behavior and its teardown paths have regression coverage, with no confirmed merge-blocking issue. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
The automated review raised no actionable items and there are no open review threads. CI is running on 1a3e87b. The diff is ready for a maintainer once CI is green. |
…andle A peer reset reaches the socket through the native close handler, which nulls _handle before destroy() runs. Save the native count there too, so bytesWritten keeps its value after the close. The http2 test now rejects on a socket, session, or request error.
|
The review of 876228a raised no actionable items. Nothing is left open on this PR. CI build 114712 is in progress. |
There was a problem hiding this comment.
Thanks — c1ec40e addresses both earlier points: detachSocket now snapshots handle.bytesWritten before nulling _handle (and the new node-net test covers the peer-reset path), and the http2 test now races each await against a failed promise wired to raw/client error. One remaining edge is noted inline.
Extended reasoning...
The follow-up commit moved the kBytesWritten snapshot into detachSocket itself, which is the single choke point both SocketHandlers.close and ServerHandlers.close go through, so the peer-initiated-close regression from the first review round is closed and now has a dedicated test in node-net.test.ts. The http2 test was reworked to wire error on raw, client, and req into a rejecting promise raced against each awaited condition. The remaining inline finding covers a separate _handle reassignment path (connection._handle = raw in the TLS-over-existing-socket upgrade) that swaps handles without going through detachSocket, so pre-wrap plaintext bytes are no longer preserved now that the per-write kBytesWritten refresh is gone.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🔴
src/js/node/net.ts— After a TLS-over-existing-socket wrap, the underlyingnet.Socketnow reportsbytesWritten === 0for the rest of its life even when plaintext (e.g. a STARTTLS command) was written before the wrap; on the base branch it kept reporting the pre-wrap count.connection._handle = rawswaps in a fresh native wrapper whosebytes_writtenstarts at 0 (socket_body.rs:3656), and with the per-writekBytesWrittenrefreshes removed the new_bytesDispatchedgetter reads that 0. Fix: preserve the pre-wrap count across every_handleswap — seed therawtwin'sbytes_writtenfrom the retired handle inupgrade_tls_impl. Same pattern at 3 sites (net.ts:2064, net.ts:2113, net.ts:2459). [also at: src/js/node/net.ts:1912 -kCloseRawConnection(TLS-over-existing-socket teardown) still assignsconnection._handle = nulldirectly, bypassing the newdetachSocketsnapshot; with the per-writekBytesWrittenrefreshes removed, the rawconnectionsocket'sbytesWritten/_bytesDispatchednow drop to 0 once the…]Extended reasoning...
Trigger:
const s = net.connect(...); s.write("EHLO\r\nSTARTTLS\r\n"); tls.connect({ socket: s, ... }). Base branch:_write(net.ts:2835 old) sets[kBytesWritten] = handle.bytesWrittenafter the plaintext write; when connect() then doesconnection._handle = rawat 2064/2113 the getter still readkBytesWritten, sos.bytesWrittenstayed at the plaintext byte count. HEAD: the per-write refresh is deleted, sos[kBytesWritten]is stillundefinedat wrap time;_bytesDispatched(net.ts:1848-1849) now readsthis._handle.bytesWritten, andrawis a brand-newTLSSocketconstructed withbytes_written: Cell::new(0)(src/runtime/socket/socket_body.rs:3656). The TLS engine writes ciphertext through thetlswrapper (which incrementstls.bytes_written), never throughraw, soraw.bytesWrittenstays 0 for the whole session and…Verification: normal — merging this change makes the underlying
net.Socket'sbytesWrittendrop to 0 immediately after a TLS-over-existing-socket wrap; the base branch preserved the pre-wrap plaintext count. Mechanism on HEAD: -Socket.prototype.connectat src/js/node/net.ts:2064 (and the deferred twin at :2113) doesconnection._handle = raw, whererawis the fresh raw-tap wrapper returned from…
Problem
net.Socketthat anode:http2session writes through reportsbytesWritten === 0and_bytesDispatched === 0. The native handle holds the correct count (67 for the script in node:net: socket.bytesWritten is 0 on a socket that a node:http2 session writes through #42465). Node reports 67 on the socket too._bytesDispatchedgetter insrc/js/node/net.ts:1846. It reads onlykBytesWritten, a copy that the JS write path refreshes after each write. The h2 frame writer writes through the native handle and never runs that path.Fix
_bytesDispatchedreadsthis._handle.bytesWrittenwhile a handle exists and falls back tokBytesWrittenwhen it does not. This is what node does (lib/net.js_bytesDispatched).bytesWrittenbuilds on_bytesDispatched.kBytesWrittenare gone. Both paths that drop the handle save the final count first:_destroyfor a localdestroy(), anddetachSocketfor the native close handler (a peer reset). So the value survives the close. The sync-fd write path has no handle and still counts intokBytesWrittendirectly.bytesWrittenspecial cases in the twonode:http2socket proxies are gone. The default case of each proxy forwards to the socket, which now returns the same value.test/js/node/http2/node-http2.test.js(new block, fails on stock bun withReceived: 0) andtest/js/node/net/node-net.test.ts(new block, guards the peer reset path). Also all ofnode-http2.test.js,node-http.test.ts,node-tls-upgrade.test.ts, and the nodetest-*-byteswritten,test-net-bytes-*,test-net-connect-buffer*files.Background
net.Socket._handleis the native socket wrapper. ItsbytesWrittengetter counts bytes the native side wrote or queued.kBytesWrittenis a symbol property on the JS socket. It now holds only the count saved at destroy time, or the count of a sync-fd socket that has no native handle.connect()reuses a handle. This PR does not change the written count on reconnect.Fixes #42465
Notes
The node-net.test.ts suite has four
net.Socket readtests,unref should exit when no more work pending,should trigger error when aborted even if connection failed #13126, andshould not leak when connect({path}) fails synchronously on a reused handlefailing in my container on main as well (ECONNREFUSED, the server bindslocalhostand the client dials 127.0.0.1). The diff does not change the result of any test in that file.Review found that the first push lost the count on a peer reset: the native close handler runs
detachSocket, which nulled_handlebefore_destroycould save the count. The second commit saves it indetachSocketand adds the net test for it.Edge cases probed on the fixed build: a detached
new net.Socket()reports 0, a connecting socket reports 0, a socket afterwrite("abcde")reports 5, and the same socket afterdestroy()still reports 5 with_handle === null.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