Skip to content

Wait for transport demand instead of failing when TLS unwrap needs a wrap #3732 - #3734

Merged
He-Pin merged 1 commit into
apache:mainfrom
pjfanning:fix-3732-tls-unwrap-need-wrap
Oct 30, 2026
Merged

He-Pin merged 1 commit into
apache:mainfrom
pjfanning:fix-3732-tls-unwrap-need-wrap

Conversation

@pjfanning

Copy link
Copy Markdown
Member

Motivation

Sometimes peer ciphertext arrives while the SSLEngine is NEED_WRAP and the ciphertext output has no demand. In that case unwrap returns 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 with IllegalStateException: 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 TLSActor fails in the same way, so this is also a reproducer for #442.

Modification

  • TlsGraphStage: inboundReady and inboundHalfClosedReady are now false while the engine needs to wrap and the ciphertext output has no demand and is not closed. onPull on the ciphertext output re-runs the pump.
  • TLSActor: the same condition (engineNotBlockedOnWrap) is added to inbound and inboundHalfClosed. The include critical TLS fix from Akka 2.6.21 (pekko-stream TLSActor) #442 comment now explains the remaining guard.
  • New TlsTransportBackpressureSpec / TlsGraphStageTransportBackpressureSpec. A plain client SSLEngine requests 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.
  • Without the fix, the TLS 1.3 cases of the new spec fail on both engines with Stuck in unwrap loop … NEED_WRAP, remaining=6. The TLS 1.2 cases pass either way.
  • scalafmt and headerCreate run. Only internal classes changed.

References

Fixes #3732, Refs #442, #3731

…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 He-Pin added the t:stream Pekko Streams label Oct 30, 2026
@He-Pin He-Pin added this to the 2.0.0-M5 milestone Oct 30, 2026
@He-Pin
He-Pin merged commit 2c34697 into apache:main Oct 30, 2026
10 checks passed
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t:stream Pekko Streams

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GraphStage TLS fails with "Stuck in unwrap loop" when peer data arrives while the engine needs to wrap and transport output has no demand

2 participants