tls: zero-copy / static-buffer read path through TLSWrap (bypass Readable chunk allocation)
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 5/5
- 预计耗时
- 一周以上
- 新手友好度
- 42/100
- Issue 类型
- 功能
- 描述清晰度
- 基本清楚
- 活跃度
- 冷清
- 技术栈
- cpp, javascript, node.js
- 领域
- backend, networking, performance
调研方向
先阅读 src/crypto/crypto_tls.cc 中的 TLSWrap::ClearOut() 和 src/crypto/crypto_tls.h 中的 kClearOutChunkSize,然后比较 stream_wrap/StreamBase 中的 TCP 静态缓冲区路径。检查 test/parallel/test-tls-onread-static-buffer.js,并确定应如何覆盖 callback、缓冲区生命周期以及 emitData 行为。完成的标准是:在保留现有 streams 路径的同时,测得 onread 在分配/复制方面有所改进。
由索引模型根据 Issue 内容生成。
描述
What is the problem this feature will solve?
Node supports onread: { buffer, callback } on TCP sockets to avoid the streams 'data' path and reuse a fixed buffer. For TLS, cleartext still goes through TLSWrap::ClearOut() which:
- Reads with SSL_read() into a stack buffer (kClearOutChunkSize = 16384 in src/crypto/crypto_tls.h)
- memcpy into a buffer from EmitAlloc()
- Delivers via EmitRead() → stream listener → JS (src/crypto/crypto_tls.cc)
So TLS consumers doing pump-style I/O (forward proxies, protocol bridges, tunnel helpers) pay at least one copy per read chunk plus Buffer/stream allocation pressure, even when they pass onread to tls.connect().
For sustained bidirectional traffic at path-MTU sizes (1280–1500 bytes for many tunnels):
- Extra copies and allocations raise CPU use and GC pauses on long-lived connections
- 16 KiB chunking can split L3 frames across reads, pushing framing/reassembly into userland
- 'data' event mode is even worse (multiple Buffer objects per second under load)
The stream Readable API is the right default for applications; pump workloads need a documented fast path that stays in native code until the user callback.
What is the feature you are proposing to solve the problem?
Extend TLSWrap so that when onread is configured on a TLSSocket, cleartext can be delivered directly into the caller's buffer without an intermediate EmitAlloc copy or Readable queue.
Proposed API / behavior:
const buf = Buffer.alloc(2048);
const socket = tls.connect({
host,
port,
rejectUnauthorized: false,
onread: {
buffer: buf,
callback(nread, buf) {
// nread > 0: process cleartext in-place
// nread < 0: error/EOF handling
},
},
// optional: suppress 'data' events when onread is used
emitData: false,
});
Implementation outline:
- In TLSWrap::ClearOut(), if an onread buffer is registered on the wrap, SSL_read() into that buffer (or a TLS-owned ring buffer with the same lifetime rules as TCP onread) and invoke the C++ → JS callback directly — mirror the TCP static-buffer path in stream_wrap / StreamBase.
- Skip EmitRead() / Readable enqueue when onread mode is active and emitData: false.
- Make read chunk size configurable or MTU-aware (e.g. default max(16384, suggestedSize) or tlsOptions.readSize) for tunnel-friendly framing.
- Document lifetime rules: callback must not retain references past return if buffer is reused; same contract as TCP onread.
Success criteria:
- Benchmark: fewer allocations and copies vs. current TLSSocket + 'data' or current onread + TLS for sustained receive workload.
- Existing test/parallel/test-tls-onread-static-buffer.js extended to assert TLS cleartext lands in the static buffer without extra Buffer churn.
- No regression when onread is not set (streams path unchanged).
What alternatives have you considered?
- Keep using 'data' events — Simple but allocates per chunk and runs through the full Readable state machine; unsuitable for high-QPS forwarding.
- TCP onread only (TLS disabled at Node layer) — Not viable for TLS-protected protocols (lockdown, HTTPS, CDTunnel over TLS); users must use TLSSocket.
- Native TLS bridge (tls.createBridge) — Best for kernel-adjacent forwarding where JS should never see bytes; heavier API. Zero-copy onread helps users who still orchestrate in JS but want a faster pump.
- Larger kClearOutChunkSize alone — Reduces SSL_read loop iterations but does not remove the memcpy/alloc to JS; does not fix frame splitting for MTU-sized traffic.
- WASM / addon OpenSSL — Works but duplicates session management; core should offer the fast path on existing TLSWrap.
- 主要语言
- JavaScript
- 星标
- 122k
- 派生
- 38.4k
- 平均合并
- 4 天 10 小时
- 30 天内合并 PR
- 276
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
nodejs/node 的其他 Issue
-
doc
难度 1/5 1 小时以内 新手友好度 90/100
维护者通常 1 天内回复
-
doc
难度 2/5 1-3 小时 新手友好度 65/100
维护者通常 1 天内回复
-
build
难度 1/5 1 小时以内 新手友好度 88/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 84/100
nodejs/node#65994 · 2 条评论 · 2 个 reaction ·
维护者通常 1 天内回复
-
feature request
难度 2/5 1-3 小时 新手友好度 68/100
维护者通常 1 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 65/100
daisy/a11y-meta-viewer#18 ·
-
good first issue status: needs triaging type: bug version: 2.0
难度 2/5 1-3 小时 新手友好度 85/100
medusajs/medusa#17094 · 2 条评论 ·
维护者通常 1 天内回复
-
browser: chrome package: @carbon/react package: styles
难度 1/5 1 小时以内 新手友好度 92/100
carbon-design-system/carbon#23567 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 88/100
clerk/javascript#10033 ·
维护者通常 1 天内回复
-
bug client p1
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复