Repository navigation
Wait for transport demand instead of failing when TLS unwrap needs a wrap #3732 - #3734
Merged
Merged
Conversation
…wrap apache#3732 Motivation: When peer ciphertext arrives while the SSLEngine is NEED_WRAP and the ciphertext output has no demand, unwrap returns the record unread. Both TLS engines still treated the inbound side as ready, re-unwrapped the same bytes and failed the stream with "Stuck in unwrap loop". TLS 1.3 hits this when the client's middlebox-compatibility ChangeCipherSpec arrives before the server has sent its whole flight. Modification: Treat the inbound side as not ready while the engine needs to wrap and the transport output has no demand (and is not closed), in both TlsGraphStage and the legacy TLSActor. Demand on the ciphertext output re-runs the pump. Add a reproducer spec for both engines. Result: Back-pressure on the ciphertext output during a handshake no longer fails the TLS stream. Tests: - sbt "stream-tests/testOnly org.apache.pekko.stream.io.TlsTransportBackpressureSpec org.apache.pekko.stream.io.TlsGraphStageTransportBackpressureSpec org.apache.pekko.stream.io.TlsSpec org.apache.pekko.stream.io.TlsGraphStageSpec org.apache.pekko.stream.io.TlsGraphStage*" - 281 passed; the TLS 1.3 cases of the new spec fail on both engines without the fix References: Fixes apache#3732, Refs apache#442
He-Pin
approved these changes
Oct 30, 2026
pjfanning
added a commit
to pjfanning/incubator-pekko
that referenced
this pull request
Oct 30, 2026
Add apache#3727, apache#3728, apache#3731, apache#3734, apache#3738 (TLS), apache#3730 (fanout publisher performance) and apache#3733 (shard lease release logging). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pjfanning
added a commit
to pjfanning/incubator-pekko
that referenced
this pull request
Oct 30, 2026
Add apache#3727, apache#3728, apache#3731, apache#3734, apache#3738 (TLS), apache#3730 (fanout publisher performance) and apache#3733 (shard lease release logging).
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.
Motivation
Sometimes peer ciphertext arrives while the
SSLEngineisNEED_WRAPand the ciphertext output has no demand. In that caseunwrapreturns the record unread. Both TLS engines still treated the inbound side as ready, so they unwrapped the same bytes again and again until the stream failed withIllegalStateException: Stuck in unwrap loop.With TLS 1.3 this happens when the client's 6-byte middlebox-compatibility ChangeCipherSpec arrives before the server has sent its whole flight. Back-pressure on the ciphertext output is legitimate, so the stage should wait for demand rather than fail.
The legacy
TLSActorfails in the same way, so this is also a reproducer for #442.Modification
TlsGraphStage:inboundReadyandinboundHalfClosedReadyare now false while the engine needs to wrap and the ciphertext output has no demand and is not closed.onPullon the ciphertext output re-runs the pump.TLSActor: the same condition (engineNotBlockedOnWrap) is added toinboundandinboundHalfClosed. The include critical TLS fix from Akka 2.6.21 (pekko-stream TLSActor) #442 comment now explains the remaining guard.TlsTransportBackpressureSpec/TlsGraphStageTransportBackpressureSpec. A plain clientSSLEnginerequests server ciphertext only when it needs to unwrap, then checks an echo after the handshake. It covers TLS 1.2 and 1.3.Result
Back-pressure on the ciphertext output during a handshake no longer fails the TLS stream on either engine.
Tests
sbt "stream-tests/testOnly org.apache.pekko.stream.io.TlsTransportBackpressureSpec org.apache.pekko.stream.io.TlsGraphStageTransportBackpressureSpec org.apache.pekko.stream.io.TlsSpec org.apache.pekko.stream.io.TlsGraphStageSpec org.apache.pekko.stream.io.TlsGraphStage*": 281 passed.Stuck in unwrap loop … NEED_WRAP, remaining=6. The TLS 1.2 cases pass either way.headerCreaterun. Only internal classes changed.References
Fixes #3732, Refs #442, #3731