child_process: frame advanced IPC messages natively#64072
Open
anonrig wants to merge 1 commit into
Open
Conversation
The `advanced` IPC read path framed messages in JavaScript
(parseChannelMessages in lib/internal/child_process/serialization.js):
every read scanned the 4-byte big-endian length prefix, accumulated
partial frames in an array, and called Buffer.concat to reassemble
messages that spanned reads, then crossed into the ipc_serdes binding
once per message to deserialize.
Move framing into a native per-channel IPCChannelFramer (a BaseObject on
the ipc_serdes binding). It owns the cross-read accumulation buffer in
C++ and returns an array of complete, deserialized messages per read, so
JavaScript no longer reframes the byte stream. Messages that lie
entirely within a read are deserialized in place and let host objects
borrow the read buffer (zero copy); only frames that span reads are
copied, matching the previous Buffer.concat behavior. The wire format
(4-byte BE length prefix + V8 payload with custom host-object tags) is
preserved byte-for-byte, and handle passing is untouched: the per-read
pendingHandle handoff and NODE_HANDLE delivery stay in setupChannel.
The `json` read path is deliberately left in JavaScript. Its
StringDecoder + split('\n') pipeline avoids copies (V8 rope
concatenation and O(1) substrings); reframing it in C++ regressed the
json read path by up to ~40% on large messages in benchmarks, so only
`advanced` is framed natively.
A cctest (test/cctest/test_node_ipc_serdes.cc) exercises the framer
directly: single-message round-trips, several messages delivered in one
read, and a frame split across two reads (including a split length
header).
Round-trip throughput
(benchmark/child_process/child-process-ipc-roundtrip, advanced;
interleaved A/B vs. the pre-change baseline, 8 reps x dur=5s):
payload before after change
1 KiB 559,040 626,255 +12.0% (t=5.3)
4 KiB 311,284 330,153 +6.1% (t=2.3)
16 KiB 104,077 125,181 +20.3%
64 KiB 31,921 35,739 +12.0%
json round-trip and read throughput are unchanged (within noise).
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
0ab8538 to
fee0262
Compare
Member
Author
Member
Author
|
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.
Summary
Move the
advancedchild_process IPC read-path framing into C++,continuing the child_process JS→C++ migration.
The
advancedcodec previously reframed every read in JavaScript(
parseChannelMessagesinlib/internal/child_process/serialization.js):scanning the 4-byte big-endian length prefix, accumulating partial frames in
an array, and
Buffer.concat-ing messages that span reads, then crossinginto the
ipc_serdesbinding once per message to deserialize.This adds a native per-channel
IPCChannelFramer(aBaseObjecton theipc_serdesbinding) that owns the cross-read accumulation buffer in C++ andreturns an array of complete, deserialized messages per read, so JavaScript
no longer reframes the byte stream. Messages contained entirely within a read
are deserialized in place and let host objects borrow the read buffer (zero
copy); only frames that span reads are copied (matching the previous
Buffer.concatbehavior).Invariants preserved
the custom host-object tags).
pendingHandlehandoff andNODE_HANDLEdelivery stay insetupChannel.jsonstays in JavaScript (deliberate)I implemented and benchmarked native
jsonframing as well, but itregressed the json read path by up to ~40% on large messages: its
StringDecoder+split('\n')pipeline is already copy-free (V8 ropeconcatenation and O(1) substrings), whereas reframing in C++ adds a buffer
copy plus a per-message
String::NewFromUtf8. Onlyadvancedis framednatively; the
jsoncodec is left unchanged.Benchmarks
benchmark/child_process/child-process-ipc-roundtrip(advanced, msgs/sec;interleaved A/B vs. the pre-change baseline, 8 reps × dur=5s):
jsonround-trip and read throughput are unchanged (a json control row in thesame harness lands within the noise floor).
Tests
test/cctest/test_node_ipc_serdes.cc: single-messageround-trip, several messages delivered in one read, and a frame split across
two reads (including a split length header).
test/parallel/test-child-process-*,test/parallel/test-cluster-*, andtest/sequential/*child*pass locally.