Repository navigation
Conversation
Collaborator
|
Review requested:
|
There was a problem hiding this comment.
🟡 Changes recommended
Additional SSL reads can let renegotiation callbacks overtake preceding application data.
1 open finding
What changed in this PR
Coalesces buffered TLS records into 16 KiB reads to reduce per-record overhead.
Changes:
- Batches decrypted records while preserving EOF and TLS error handling.
- Adds coalescing, small-buffer, EOF, and corruption tests.
- Updates fragment-size validation to inspect wire records.
| File | Description |
|---|---|
src/crypto/crypto_tls.cc |
Implements record coalescing and deferred error emission. |
src/crypto/crypto_tls.h |
Declares extracted TLS error helpers. |
test/parallel/test-tls-coalesce-records.js |
Adds coalescing regressions. |
test/parallel/test-tls-max-send-fragment.js |
Validates fragment sizes on the wire. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
ronag
requested review from
jasnell and
mcollina
and
a balanced review from Copilot
October 13, 2026 08:33
ronag
marked this pull request as ready for review
October 13, 2026 09:48
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #66698 +/- ##
==========================================
- Coverage 92.82% 90.49% -2.34%
==========================================
Files 422 797 +375
Lines 194189 277791 +83602
Branches 30039 53367 +23328
==========================================
+ Hits 180257 251382 +71125
- Misses 13607 16797 +3190
- Partials 325 9612 +9287
🚀 New features to boost your workflow:
|
ronag
marked this pull request as draft
October 17, 2026 07:29
ronag
force-pushed
the
codex/tls-coalesce-buffered-records
branch
2 times, most recently
from
October 17, 2026 07:57
ab55637 to
9f3bab3
Compare
ronag
force-pushed
the
codex/tls-coalesce-buffered-records
branch
from
October 17, 2026 08:06
9f3bab3 to
435181a
Compare
SSL_read() returns at most one TLS record, and ClearOut() emitted a separate read for each one. Peers that use small records, such as nginx with its default 4 KiB ssl_buffer_size or servers doing dynamic record sizing, made every record pay for its own buffer allocation and call into JavaScript. While the next buffered record is application data, keep reading into the same 16 KiB buffer before emitting it, so reads never get larger than one full record already makes them. Batching stops before any other record type, so that e.g. a TLS 1.2 renegotiation is still processed after the data in front of it has been emitted. It also stops before records short enough to be empty, because SSL_read() skips empty records and carries on with the next one. If SSL_read() fails in the middle of a batch, the error is captured first and reported after the data. test-tls-max-send-fragment relied on 'data' chunks matching records, so it now checks the record sizes on the wire. Signed-off-by: Robert Nagy <ronagy@icloud.com> Assisted-by: claude:opus-5.5
ronag
force-pushed
the
codex/tls-coalesce-buffered-records
branch
from
October 17, 2026 08:14
435181a to
7ae2c5a
Compare
Member
|
There is a relevant CI failure |
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.


SSL_read()returns at most one TLS record per call, andTLSWrap::ClearOut()emits a separate read for each one. Records can be up to 16 KiB, but plenty of servers send smaller ones: nginx defaultsssl_buffer_sizeto 4 KiB, and servers that do dynamic record sizing start out at around 1 KiB. Every record then pays for its own buffer allocation and call into JS.With this change
ClearOut()keeps callingSSL_read()into the same 16 KiB buffer while the next record inenc_in_is application data. It never waits for more input, it only merges records that have already arrived, and reads never get bigger than one full record already makes them.Some notes:
SSL_read()skips empty records and goes straight on to the next one. In TLS 1.2 that means handshake and alert records are still processed after the preceding data has been emitted, so a'data'handler can e.g. still calldisableRenegotiation()before a renegotiation that arrived in the same read.test-tls-coalesce-renegotiationcovers that, and fails if the record type check is removed.SSL_read()fails in the middle of a batch,SSL_get_error()and the error are still read right away, as before. The batched data is emitted next, then the error or EOF.'session'or'keylog'can now be emitted before the'data'that came before it. There is no documented ordering between those events and'data'. Keeping it would mean emitting data from inside OpenSSL callbacks, with the reentrancy problems that brings, so I didn't.'data'chunks no longer line up with records, sotest-tls-max-send-fragmentnow checks the record sizes on the wire through a small proxy.pummel/test-https-no-readerfails), so I kept 16 KiB.benchmark/tls/throughput-s2c.jsgets arecordlenoption, which sets the server'ssetMaxSendFragment().Results on an AMD EPYC 9355P (Linux, Docker, 16 CPUs on one NUMA node, benchmark pinned to one CPU), 10 runs each, MiB/s: