node: internalBinding http2/StreamBase/js_stream_socket handle shims v26 compat (+16 tests) - #35522
Draft
cirospaciari wants to merge 14 commits into
Draft
node: internalBinding http2/StreamBase/js_stream_socket handle shims v26 compat (+16 tests)#35522cirospaciari wants to merge 14 commits into
cirospaciari wants to merge 14 commits into
Conversation
…or messages The merge of claude/node-v26-permission-wave2 dropped the reject_bad_negations field from the ParseOptions initializer in Arguments.rs (added by the cli negation-errors commit), breaking the build, and resolved three node_fs.rs call sites back to the pre-parity generic 'path must be a string' errors, orphaning PathOrFdExt::from_js_required. Restores the field and the from_js_required calls; deletes BUFFER_EXPECTED_TYPES, superseded by throw_invalid_argument_type_list at its only former call site.
…tants - kNghttp2ErrorMessages now carries the full nghttp2_strerror() table instead of an 11-entry subset, so NghttpError messages match node for every code. - http2.constants gains the 13 constants node's binding registers with NODE_DEFINE_HIDDEN_CONSTANT (NGHTTP2_HCAT_*, NGHTTP2_NV_FLAG_*, NGHTTP2_ERR_*, STREAM_OPTION_*), defined non-enumerably like node so Object.keys(constants) is unchanged. - The test-only internals surface (Symbol.for'd, consumed by the vendored suite's --expose-internals shim) additionally exposes nghttp2ErrorString and createPendingStreamCancelError.
socket.destroy() followed by the native close event ran finishSocketEnd, which push(null)'d a graceful 'end' the way a peer FIN would. Node never emits 'end' after a local destroy (the readable is already destroyed). Guard the EOF synthesis on !self.destroyed; peer-FIN delivery on live sockets is unchanged. Covered by the vendored test-wrap-js-stream-destroy (its server asserts 'end' is not emitted after socket.destroy()), which failed on exactly this.
…util (+7 tests)
Node's http2 *-errors tests stub methods on internalBinding('http2')'s
Http2Session/Http2Stream prototypes to inject nghttp2 error codes into the
JS layer. Bun drives its engine directly with no handle layer, so the
test-only binding now exposes stand-in handle classes and, on first use,
wraps the real node:http2 entry points that node routes through the handle
(request/respond/respondWithFile/additionalHeaders/pushStream) to consult
those prototypes at call time and replicate node's error dispatch from
lib/internal/http2/core.js (ERR_HTTP2_OUT_OF_STREAMS /
ERR_HTTP2_STREAM_SELF_DEPENDENCY / NghttpError, session-vs-stream targets).
With nothing stubbed every wrapper falls through to the real implementation,
so the shim is inert outside these tests.
internal/http2/util.js is vendored byte-identical from node v26.3.0; the
require interceptor recovers the live NghttpError/assert helpers and kSocket
from node:http2's internals so instanceof checks keep matching, and bridges
the public http2.sensitiveHeaders symbol onto the vendored module's private
one for buildNgHeaderString. The binding also carries the shared state
arrays (settingsBuffer/optionsBuffer/sessionState/streamState) and
refreshDefaultSettings the vendored module destructures.
Vendored (all pass, byte-identical to upstream):
test-http2-client-onconnect-errors, test-http2-info-headers-errors,
test-http2-respond-nghttperrors, test-http2-respond-with-fd-errors,
test-http2-server-push-stream-errors, test-http2-util-headers-list,
test-http2-util-update-options-buffer
…_wrap (+9 tests)
The test-only binding now implements the StreamBase JS-handle protocol the
stream_wrap/js_stream/tcp_wrap tests exercise:
- internalBinding('stream_wrap'): WriteWrap/ShutdownWrap request classes and
the shared streamBaseState array (kReadBytesOrError/kArrayBufferOffset/
kBytesWritten/kLastWriteWasAsync, src/stream_base.h layout).
- internalBinding('js_stream').JSStream: node's JS-callback-driven handle
(readBuffer/emitEOF/finishWrite/finishShutdown + onread/onwrite/onshutdown
hooks). Extends TextEncoder so instances stay host objects — the vendored
worker tests require postMessage(new JSStream()) to throw DataCloneError.
- internalBinding('tcp_wrap').TCP: bind/bind6/getsockname/listen/connect/
readStart/writeBuffer/shutdown over Bun.listen/Bun.connect (which bind
synchronously, like uv_tcp_bind). The raw-fd path the vendored dgram tests
use (handle.fd + zero-argument listen()) is preserved.
internal/js_stream_socket is a nodeinternals shim keeping node's class
surface (doShutdown/finishShutdown/doWrite/finishWrite/doClose, pending-
write/shutdown bookkeeping verbatim) with the Duplex plumbing node gets from
net.Socket({ handle }) written out explicitly, since Bun's net.Socket has no
JS-handle StreamBase layer. internal/webstreams/adapters vendors the two
StreamBase-backed adapters verbatim; the interceptor merges them under Bun's
real adapters module.
Vendored (all pass, byte-identical to upstream):
test-wrap-js-stream-{destroy,duplex,exceptions,read-stop},
test-js-stream-call-properties, test-tcp-wrap, test-tcp-wrap-connect,
test-tcp-wrap-listen, test-whatwg-webstreams-adapters-streambase
Also fixed by this + the net 'end' fix: the already-vendored
test-stream-wrap, test-stream-wrap-drain, test-stream-wrap-encoding
(quarantined in test/expectations.txt lines 53-55; removing those lines
needs a main-side expectations PR).
The claude/node-v26-permission-wave2 merge resolved Arguments.rs back to a pre-parity state: the -e/-p params lost their node-style value-binding markers and the --print/--eval handling no longer set eval.provided, which is what Command dispatch selects eval mode on — every bun -e/-p/--eval invocation printed the intro help and exited 0. Restores the param markers and the final --print/--eval block from the cli value-binding commit (3c39e30). Same damage class ea236c1 repaired.
…S validation lost in branch merge Second half of the same merge damage: the parse error path lost Node's '<execPath>: <flag> requires an argument' / invalid-negation reporting (exit 9), and the node-standing commands lost NODE_OPTIONS validation with Node's disallowed-option contract. Content restored verbatim from the cli value-binding commit (3c39e30); the clap-side machinery survived the merge, so this is Arguments.rs only.
…sing lost in branch merge Third tranche of the same merge damage: the runtime param tables lost the --check declaration (with -c handed to it on the node-standing commands via BASE_PARAMS_NO_CONFIG_SHORT_), --input-type (dispatch read the option but no table declared it), and --inspect-port/--debug-port. Restored from the cli --check commit (fd183a8), except its permission-flag rejection block and duplicate --permission/--allow-fs-* params: the merged permission model (claude/node-v26-permission-wave2) now parses and enforces those, so the old loud rejection must not resurface.
Collaborator
…aude/node-binding-shims-v26 # Conflicts: # src/runtime/cli/Arguments.rs
…aude/node-binding-shims-v26 # Conflicts: # src/js/internal/test/binding.ts
…le parent_ref in web_worker.rs
…aude/node-binding-shims-v26 # Conflicts: # src/jsc/bindings/BunHeapProfiler.h
…aude/node-binding-shims-v26
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.
Adds the internalBinding/native-handle test families: the http2
*-errorssuite, the StreamBase JS-handle protocol suite (stream_wrap / js_stream / tcp_wrap), and theinternal/js_stream_socketsuite. 16 new upstream tests vendored byte-identical from Node v26.3.0, all passing; 3 more already-vendored tests (test-stream-wrap, test-stream-wrap-drain, test-stream-wrap-encoding) now pass but stay quarantined in test/expectations.txt lines 53-55 (removing those needs a main-side expectations PR).What was missing
These tests monkey-patch methods on node's native handle prototypes (
internalBinding('http2').Http2Stream.prototype.respond = () => code,internalBinding('js_stream').JSStream,internalBinding('tcp_wrap').TCP) to inject failures or drive I/O by hand. Bun's runtime drives its engines directly and has no handle layer, sointernalBindingreported them unimplemented.Shims (test-only layer)
All in
src/js/internal/test/binding.ts(expose-internals-gated) andtest/js/node/test/common/nodeinternals/:Http2Session/Http2Streamhandle classes; the firstinternalBinding('http2')call wraps the real node:http2 entry points node routes through the handle (request / respond / respondWithFile / additionalHeaders / pushStream) to consult those prototypes at call time and replicate node's dispatch fromlib/internal/http2/core.js(ERR_HTTP2_OUT_OF_STREAMS/ERR_HTTP2_STREAM_SELF_DEPENDENCY/NghttpError; session-vs-stream error targets;pushStream's callback delivery). With nothing stubbed every wrapper falls through to the real implementation, so the shim is inert outside these tests.internal/http2/util.jsis vendored byte-identical; the harness recovers the liveNghttpError/assert helpers soinstanceof/constructorchecks match, and bridges the publichttp2.sensitiveHeaderssymbol onto the vendored module's private one.WriteWrap/ShutdownWrapand the sharedstreamBaseStatearray (src/stream_base.hlayout), plus a JSJSStreamimplementing node's callback-driven handle (readBuffer / emitEOF / finishWrite / finishShutdown; extends TextEncoder so structured clone still rejects instances as host objects, which the worker transfer tests assert).TCPwith bind/getsockname/listen/connect/readStart/writeBuffer/shutdown overBun.listen/Bun.connect(both bind synchronously, likeuv_tcp_bind). The raw-fd surface the vendored dgram tests use is preserved.net.Socket({ handle })written out explicitly, since Bun's net.Socket has no JS-handle StreamBase layer.Runtime fixes found by the tests
src/js/node/http2.ts):kNghttp2ErrorMessagesnow carries the fullnghttp2_strerror()table (was an 11-entry subset, so mostNghttpErrormessages read "Unknown error code");http2.constantsgains the 13 constants node registers withNODE_DEFINE_HIDDEN_CONSTANT(NGHTTP2_HCAT_*,NGHTTP2_NV_FLAG_*,NGHTTP2_ERR_*,STREAM_OPTION_*), defined non-enumerably like node soObject.keys(constants)is unchanged.src/js/node/net.ts):socket.destroy()followed by the native close synthesized a graceful'end'event node never emits (fixing line: theself.destroyedguard infinishSocketEnd). Covered by test-wrap-js-stream-destroy, which fails without it.Base repairs (merge damage on claude/node-v26-fix-tls)
The permission-wave2 merge (3d683b2) dropped whole generations of the cli-parity work while keeping its tests. On the unfixed base, every
bun -e/-p/--evalinvocation prints the intro help and exits 0. Restored, content taken verbatim from the surviving commits (3c39e30 / fd183a8) with the permission-model pieces of the old world excluded so wave2's--permissionhandling is untouched:reject_bad_negations, fs path-arg messages; base does not compile without it).-e/-pvalue binding +eval.provideddispatch (this is what un-breaks eval).--checksyntax mode,--input-type,--inspect-port/--debug-portdeclarations.run-eval.test.ts goes 50 pass/41 fail → 88 pass/3 fail on this branch; the vendored test-cli-{bad-options,node-options-disallowed,syntax-*} suite (already in the base, failing there) passes except test-cli-bad-options. The remaining failures are permission-model contract (
--allow-fs-readwithout--permissionmust exit 1; three stale run-eval cases assert the pre-wave2 "--permission is not supported" rejection) — that belongs to the permission-model owner, not changed here.Verified
bun test test/js/node/net/(219 pass/0 fail), and all 145 vendored test-net-* files (2 failures, both keepalive-delay-unit issues reproduced on the unfixed base).Out of scope, flagged
_handle.asyncResetin the http agent; not vendored.