Repository navigation
node:net: return false from write() when the send fails at once - #43755
Merged
Jarred-Sumner merged 1 commit intoSep 22, 2026
Merged
Conversation
Socket.prototype._write deferred a send that the kernel rejects (ECONNRESET, EPIPE) and a write on a closed handle (EBADF) to the next tick. The stream was not errored when write() returned, so write() returned true. Node hands the errno to the stream callback inside the same call (afterWriteDispatched in lib/internal/stream_base_commons.js). The stream marks itself errored at once and write() returns false. The stream still runs the write callback and destroys the socket on the next tick, so the order of the write callback, 'error' and 'close' does not change.
Contributor
|
Warning Review limit reached
This review includes 3 billable files and costs up to $0.75.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Or wait 5 minutes for your next included review. View limit detailsLimit details: You’ve used all 10 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Repository: oven-sh/bun/.coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (3)
Comment |
Collaborator
Author
|
Status
|
Jarred-Sumner
deleted the
robobun/c900f15b/net-write-sync-failure-returns-false
branch
September 22, 2026 04:53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
net.Socket#write()returnstruewhen the send fails inside the same call: the kernel rejects it (ECONNRESET,EPIPE) or the handle is already closed (EBADF). Node v26.3.0 returnsfalseand setssocket.erroredbeforewrite()returns.Socket.prototype._write(src/js/node/net.ts:2968and:2976). It hands the error to the stream callback withprocess.nextTick.Writable.write()computes its return value before that tick.Fix
_writecalls the stream callback with the error in the same call, in both branches, like Node'safterWriteDispatched(stream_base_commons.js#L158-L159).'error'and'close'does not change._writedoes not callfailWritethere.failWritedestroys the socket at once, so a second write in the same tick would getERR_STREAM_DESTROYED. Node gives both writes the original errno.test/js/node/net/node-net.test.tsandtest/js/node/net/net-syscall-fault.test.ts(8 new tests, all fail on main'ssrc/). Also node'stest-net-*,test-http-*,test-tls-*andtest-http2-*files.Background
Writable.write()calls_write(chunk, encoding, callback). Ifcallback(err)runs before_writereturns, the stream setserroredat once andwrite()returnsfalse. The user callbacks and the destroy wait for the next tick.$writereturns a negative errno whensend()fails because the peer is gone.Downsides
socket.erroredorsocket.writableright after a failedwrite()sees the failure one tick earlier. Node reports the same values.true. The native TLS write does not report the errno to_write(tls: report a rejected send() as a write error instead of a clean close #42336 adds that).Notes
Repro (from a differential run of bun against node):
node v26.3.0 and this branch (Linux and Windows, line for line):
bun 1.4.3 and main:
The same comparison holds for a unix socket whose peer closed (
EPIPE), forsocket._handle.close()followed bywrite()(EBADF,EPIPEon Windows), forcork()/uncork(), and forend(data).#43705 notes this bug as found on the way and leaves it alone. The two changes are independent.
Why the stream still destroys the socket. The
Socketconstructor forcesautoDestroy: true.Writable'sonwriteseesstate.sync, so it queuesonwriteErrorwithprocess.nextTick.onwriteErrorruns the write callback, fails the buffered writes with the same error, and callsdestroy(err). This is the path the deferredfailWritecall reached before, one tick later.Tests
node-net.test.ts: a unix socket whose peer closed (POSIX, the kernel reports it inside the peer'sclose(2)), a TCP socket whose peer reset the connection, a handle that was closed directly, and a corked batch thatuncork()sends through_writev. The TCP test takes a fresh connection until the kernel rejects the send, because loopback does not promise that the RST is processed before the nextsend(2). On Linux and Windows the first connection hit in 500 of 500 runs (release builds), so the loop is a guard for other kernels.net-syscall-fault.test.ts: an injectedECONNRESET/EPIPEon the firstsend(), for the connecting socket and for the accepted socket. These run on ASAN builds only, where the fault hooks exist.node-net.test.tsis 114 pass, 0 fail.Suites run on the Linux debug build:
test/js/node/net/*.test.ts,test/js/node/tls/node-tls-{connect,server,upgrade}.test.ts,tls-syscall-fault.test.ts, and thetest-net-*,test-http-*,test-https-*,test-tls-*,test-http2-*,test-child-process-*,test-cluster-*files oftest/js/node/test. Every failure there also fails without this change: the container resolveslocalhostto127.0.0.1only whileBun.listen("localhost")binds::1, and a few tests exceed their timeout on a debug build. On Windows,test-net-pingpong.jsfails with and without this change (#39723, with #39727 open for it).TLS. After the peer's raw socket resets, node's
tlsSocket.write()also returnsfalsewithECONNRESET. Bun returnstrue, reports the write as done and closes without an error, with and without this change. The native TLS write never returns the errno, so_writecannot see the failure. #42336 makes the native side report it. With both changes the TLS path reaches the sameres < 0branch.Not changed:
tlsSocketWriteinsrc/js/node/_http2_upgrade.tstreats any truthy$writeresult as success. A TLS handle never returns a negative errno today, so that path is unaffected.