Repository navigation
Conversation
Collaborator
Author
|
How this was reproduced (linux x64) A script connects a
This PR is a draft until #46522 and #46528 merge. CI (commit 8c1b801, build 135206)
|
Collaborator
Author
|
Updated 10:35 AM PT - Oct 29th, 2026
❌ @robobun, your commit 8c1b801 has 2 failures in
🧪 To try this PR locally: bunx bun-pr 47331That installs a local version of the PR into your bun-47331 --bun |
net.Socket.prototype.setKeepAlive follows Node.js v26.12.1: four arguments or an options object, four stored values in seconds, and the replay of all four after connect. A node:net-private binding gives them to us_socket_keepalive_ex, which writes TCP_KEEPINTVL and TCP_KEEPCNT, also for a socket that sent its FIN. A function put over the handle's setKeepAlive() gets Node's call first. Bun.Socket#setKeepAlive and us_socket_keepalive keep 1 second and 10 probes.
… kernel node-net-keepalive.test.ts reads the socket options back with getsockopt. node-net-keepalive-handle.test.ts also runs under Node.js. test-net-keepalive-interval-count.js and test-net-connect-keepalive.js are Node's, unmodified.
robobun
force-pushed
the
robobun/6c4a7c87/net-setkeepalive-interval-count
branch
from
October 29, 2026 11:00
b6eb085 to
052a4e2
Compare
The type check that `bun build --compile --aot` runs on the modules of Bun did not narrow `setKeepAliveEx` after `??= $bindgen(...)`. The call on the next line failed with TS2722 in `compile::tests::every_module_of_bun_keeps_its_types`. The cast gives the assignment a type, as the other lazy bindings of the file have. The emitted JavaScript is the same.
pipeline/gc/custom-accessor-reached-computed-key.ir spells out the set that `process.env[key]` can answer. The set depends on the ids of the built-ins. A new function in the table gives each entry behind it another id, and the set gains builtin(FunctionPrototype). The test passes on main. It fails on a branch that adds a function to the table, as this branch does with setKeepAliveEx. The set is not the subject of the test, so its lines accept both sets. #47067 has the same change.
This branch has not been deployed
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.
Draft until #46522 and #46528 merge: they rewrite the same lines.
Problem
net.Socket#setKeepAlive()ignoresintervalandcount, and reads an options object astrue. SosetKeepAlive({ enable: false })turns keep-alive on. Node.js added both in v26.4.0 (net: support TCP_KEEPINTVL and TCP_KEEPCNT in setKeepAlive nodejs/node#63825).setKeepAlive(src/js/node/net.ts:3257) takes two arguments, andbsd_socket_keepalive(packages/bun-usockets/src/bsd.c:563) writesTCP_KEEPINTVL=1andTCP_KEEPCNT=10.Fix
setKeepAlivefollowslib/net.jsof v26.12.1: four arguments or an options object, kept in seconds and replayed after connect.us_socket_keepalive_extakes the interval and the count, on Windows too. Only node:net reaches it, throughsetKeepAliveEx.Bun.Socket#setKeepAlive, fetch, Postgres and MySQL issue the samesetsockoptcalls.node-net-keepalive.test.tsreads the options withgetsockopt(main fails 61 of 87). Node'stest-net-keepalive-interval-count.jspasses unmodified.Background
TCP_KEEPIDLEidle seconds the kernel probes everyTCP_KEEPINTVLseconds and closes the socket afterTCP_KEEPCNTunanswered probes._handleof anet.Socketis aBun.Socket, whosesetKeepAlive(enable, milliseconds)has no interval. node:net now keeps seconds and calls the private binding, which reverses the node:net half of Bun.Socket: make setKeepAlive honor documented milliseconds and fix setKeepAlive(true) returning false #34269.Bun.Socket#setKeepAlivearguments: nobody asked for that API. Considerednet.tsonly:intervalwould still write 1 s.Downsides
setKeepAlive(object)now readsobject.enable: any object used to turn keep-alive on. A BigIntintervalnow throws, as in Node.js.new net.Socket({ handle }), or a function put over_handle.setKeepAlive, gets Node's seconds, not milliseconds.setsockoptcalls per enable, was 2.Notes
The rebase after #46522 and #46528. Both are open and both rewrite these lines.
applySocketOptions(self, handle). That function then callssetHandleKeepAlive(self, handle). Itshandle.setKeepAlive(true, self[kSetKeepAliveInitialDelay])would give seconds to a method that takes milliseconds. The tests "before the connection exists" fail if that line stays.server.keepAliveInitialDelayseconds. The two accept seeds then store it as it is, andkeepAliveDelayOf()goes. Node'stest-net-server-keepalive.jscan then run unmodified: on this base its only failure is1 !== 1000.set_keep_alive_exinsrc/uws_sys/socket.rsthen needs theUwsHttparm thatset_keep_alivehas there, and a node:http row in the test.Kernel state (linux x64, read from outside the process with
pidfd_getfdandgetsockopt). Cells are SO_KEEPALIVE / TCP_KEEPIDLE / TCP_KEEPINTVL / TCP_KEEPCNT. 7200 / 75 / 9 are the defaults of the kernel.setKeepAlive({ enable: false })setKeepAlive({ enable: true, initialDelay: 5000, interval: 3000, count: 7 })setKeepAlive(true, 5000, 3000, 7)setKeepAlive(true, 5000)setKeepAlive(true, 5000, 0, 7)end(), thensetKeepAlive(true, 5000, 3000, 7)55 of 59 probed calls equal Node.js v26.12.1 (main: 16).
The 4 calls that still differ.
setKeepAlive(true),(true, 0),(true, 500)and(true, 5000, 0, 7), when the call comes afterconnect()and before the connection exists. Node.js ends at 1 / 60 / 1 / 10: libuv writes 60 / 1 / 10 when it opens a socket whose handle has the keep-alive flag (deps/uv/src/unix/stream.c:417), and Node's own call writes nothing for these values. Bun leaves 7200 / 75 / 9. Main gives the same for the first three and 1 / 5 / 1 / 10 for the fourth. The same four calls beforeconnect()equal Node.js. The test has these calls astest.todo, and one more for atls.TLSSocketthat wraps a socket which still connects.Invalid values follow the implementation of Node.js, not its documentation.
UV_EINVALfirst. So doesnull,NaNandInfinityas interval.-0) becomes 10. A negative interval becomes 1 s. A string interval is a number after the division.interval, a conversion that throws, and options whose getter throws or whoseinitialDelayis a BigInt or a Symbol.A function over
_handle.setKeepAlive. The method of a Bun socket takes milliseconds, so node:net does not call it. A function that replaces it gets Node's call first, then the socket gets the values through the private binding. So a function that passes the call on to the method it replaced still ends with the right values. Three cases differ from Node.js: a function that swallows the call does not stop the write, a function that passes on a zero interval leaves what the method wrote, and one that passes on a negative delay throws.Measurements (release builds of main at bf80135 and of this PR on that commit, linux x64).
size: text 138,012,946 to 138,015,755 B (+2,809), data and bss equal. Keep-alive symbols (nm -S): 1,408 to 2,671 B.setKeepAliveEx), feature-table names: +1, public methods changed: 0.perf_event_open, 10 interleaved runs,BUN_JSC_useConcurrentJIT=0, median and range):setKeepAlive(true, 1000): 32.0 (31.7 to 32.0) to 6.0 (5.7 to 6.1) for each callnew net.Socket(): 1,923 (1,912 to 1,933) to 1,924 (1,913 to 1,941), inside the rangerequire("node:net"): 22,823,880 (22,812,495 to 22,842,511) to 22,860,082 (22,851,266 to 22,872,621) for the processsetKeepAlive(true, 5000): 18 to 20, as in Node.js.setsockoptcalls, main to this PR (Node.js v26.12.1), counted with anLD_PRELOADlogger:(true, 5000)(true, 5000, 3000, 7)(true, 5000, 0, 7)(true, 5000, 3000, 7)then(true, 5000)http.AgentBun.connect(setKeepAlive(true, 4000),(true),(true, 0),(false)): thesetsockoptlogs of main and of this PR have 0 differing lines.setsockoptcall sites as before, the four-value entry has 4.Tests.
test/js/node/net/node-net-keepalive.test.ts: release build 87 pass, 5 todo. Main: 61 fail. Debug build with ASAN: 86 pass, 1 skip (the cluster test starts two processes).test/js/node/net/node-net-keepalive-handle.test.tsusesnode:test. Its last test runs the file under Node.js when that is v26.4.0 or later. Node.js v26.12.1: 3 pass. Main: 3 fail.test-net-keepalive-interval-count.jsandtest-net-connect-keepalive.jsare from Node.js v26.12.1, unmodified. Both fail on main. test: prune stale entries from expectations.txt #36780 removed the second one.node-net.test.tsis intest/flaky-tests.txt, where a pass on a retry is not reported as new, and the Windows and macOS arms of this change ran nowhere before CI.node-net.test.ts(the same 2 failures as main on this machine),server.spec.ts,node-net-server.test.ts,node-net-allowHalfOpen.test.js,fetch-tcp-keepalive.test.ts,socket.test.ts -t setKeepAlive,visible-surface.test.ts,members-of-prototype-objects-for-any-receiver.test.ts, and the 6 older keep-alive files oftest/js/node/test/parallel.bun run rust:check-allforx86_64-pc-windows-msvcandaarch64-apple-darwin: ok. The C of the Windows arm was not compiled here.cargo test -p bun_aot: 2,893 and 87 pass. It has the type check thatbun build --compile --aotruns on the modules of Bun.test/aot/engine-tests/pipeline/gc/custom-accessor-reached-computed-key.ir: the lines that spell out a set of built-ins now accept either set. The set depends on the ids of the built-ins, andsetKeepAliveExgives each entry behind it another id. Replace picohttpparser with safe Rust #47067 has the same change.Left as it is.
server.keepAliveInitialDelaystays in milliseconds on this base, and past 2^32 ms it wraps (net, tls: consolidate the open node:net, node:tls, Bun.connect, Bun.listen and usockets pull requests #46522).setKeepAlive()is a stub there (node:http: make setKeepAlive() and the keepAlive option act on server sockets #45006, http: consolidate the open fetch, Bun.serve, node:http, node:http2 and HTTP/3 pull requests #46528).setKeepAliveon Bun, soSO_KEEPALIVEreaches a unix fd.Bun.Socket#setKeepAlive, fetch, Postgres and MySQL write no interval and no count. Windows uses 1 s and 10 by itself.us_socket_keepalive(the two-value entry) still skips a socket that sent its FIN.Self-review. 12 concerns. Two are done in part only.
keepAliveInitialDelaypast 2^32 ms. net, tls: consolidate the open node:net, node:tls, Bun.connect, Bun.listen and usockets pull requests #46522 changes the unit of that property, and a row here would pin the value that it removes. The case is under "Left as it is".Other open PRs. #47138 adds a sentence about this gap to
docs/runtime/nodejs-compat.mdx. If it merges first, this PR removes the sentence. If this PR merges first, #47138 drops it. #46789 retypes the publicsetKeepAlivebinding, and #45531 adds a neighbour inbsd.c.no test proof · iteration 2 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/node/net/node-net-keepalive.test.ts