pjfanning commented on code in PR #3728:
URL: https://github.com/apache/pekko/pull/3728#discussion_r4406882497


##########
stream/src/main/scala/org/apache/pekko/stream/impl/io/TlsGraphStage.scala:
##########
@@ -679,18 +679,13 @@ import pekko.util.ByteString
         userInBuffer = null
       }
 
-      private def acquireTransportBuffer(requiredCapacity: Int): ByteBuffer = {
-        val buffer = bufferPool.acquire()
-        if (buffer.capacity >= requiredCapacity) buffer
-        else {
-          releaseTransportBuffer(buffer)
-          throw new IllegalStateException(
-            s"TLS packet buffer requires $requiredCapacity bytes but 
pekko.io.tcp.direct-buffer-size is $pooledBufferSize")
-        }
-      }
+      private def acquireTransportBuffer(requiredCapacity: Int): ByteBuffer =
+        if (requiredCapacity <= pooledBufferSize) bufferPool.acquire()
+        else ByteBuffer.allocate(requiredCapacity)
 
       private def releaseTransportBuffer(buffer: ByteBuffer): Unit =
-        if (buffer ne null) {
+        // Only direct buffers came from the TCP pool; larger heap buffers are 
stage-owned.
+        if ((buffer ne null) && buffer.isDirect) {

Review Comment:
   Using `isDirect` to decide whether a buffer belongs to the pool is fragile. 
If the fallback or growth path later switches to `allocateDirect` (a natural 
optimisation), oversized stage-owned buffers would be released into the shared 
fixed-size TCP pool, and TCP connections would get buffers of unexpected size. 
Suggest tracking ownership explicitly, e.g. a flag per buffer or at least 
`buffer.capacity == pooledBufferSize`.



##########
stream/src/main/scala/org/apache/pekko/stream/impl/io/TlsGraphStage.scala:
##########
@@ -679,18 +679,13 @@ import pekko.util.ByteString
         userInBuffer = null
       }
 
-      private def acquireTransportBuffer(requiredCapacity: Int): ByteBuffer = {
-        val buffer = bufferPool.acquire()
-        if (buffer.capacity >= requiredCapacity) buffer
-        else {
-          releaseTransportBuffer(buffer)
-          throw new IllegalStateException(
-            s"TLS packet buffer requires $requiredCapacity bytes but 
pekko.io.tcp.direct-buffer-size is $pooledBufferSize")
-        }
-      }
+      private def acquireTransportBuffer(requiredCapacity: Int): ByteBuffer =
+        if (requiredCapacity <= pooledBufferSize) bufferPool.acquire()

Review Comment:
   This drops the check on the acquired buffer's real capacity. It now assumes 
every buffer from `bufferPool` has exactly `pooledBufferSize` (`Tcp` 
`DirectBufferSize`) capacity. If those ever diverge, an undersized buffer comes 
back silently and fails later as unexplained overflow/underflow loops, where 
the old code failed fast here. Suggest keeping the check: acquire, and if 
`buffer.capacity < requiredCapacity`, release it and allocate on the heap 
instead.



##########
docs/src/main/paradox/stream/stream-io.md:
##########
@@ -161,6 +161,11 @@ Java
 
 The `SSLEngine` instance can then be used with the binding or outgoing 
connection factory methods.
 
+When using `pekko.stream.materializer.tls.engine = graph-stage`, TLS packet 
buffers are sized to
+the requirements of the `SSLEngine`. The `pekko.io.tcp.direct-buffer-size` 
setting controls the

Review Comment:
   "It does not limit the TLS record size" is accurate, but it undersells the 
effect. `direct-buffer-size` still decides whether TLS uses pooled direct 
buffers or heap buffers allocated per connection (3 × ~16.7 KiB), and it still 
sets batching (`pooledBufferSize / packetSize`, so 1 record per engine call 
below ~16.7 KiB). Worth saying that lowering it below the TLS packet size has a 
performance cost.



##########
stream-tests/src/test/scala/org/apache/pekko/stream/io/TlsGraphStageIsolatedSpec.scala:
##########
@@ -247,19 +248,68 @@ class TlsGraphStageIsolatedSpec extends 
StreamSpec(TlsSpec.configOverrides) with
   }
 }
 
+class TlsGraphStageSmallBufferSpec
+    extends StreamSpec(
+      ConfigFactory.parseString("pekko.io.tcp.direct-buffer-size = 8192")
+        .withFallback(ConfigFactory.parseString(TlsSpec.configOverrides))) {
+
+  "TlsGraphStage" must {
+    for (protocol <- Seq("TLSv1.2", "TLSv1.3")) {
+      s"round-trip $protocol records larger than the TCP pool buffers" in {
+        assertAllStagesStopped {
+          val context = TlsSpec.initSslContext(protocol)
+          def tls(client: Boolean) =
+            BidiFlow.fromGraph(new TlsGraphStage(
+              () => {
+                val engine = context.createSSLEngine()
+                engine.setUseClientMode(client)
+                engine.setEnabledProtocols(Array(protocol))
+                engine
+              },
+              _ => Success(()),
+              
IgnoreComplete)).withAttributes(TlsGraphStage.StreamTlsAttributes)
+
+          val payload = 
ByteString.fromArray(Array.tabulate[Byte](32768)(_.toByte))
+          val echo = Flow[SslTlsInbound].collect { case SessionBytes(_, bytes) 
=> SendBytes(bytes) }
+          val received = Source.single[SslTlsOutbound](SendBytes(payload))
+            .via(tls(client = true).atop(tls(client = 
false).reversed).join(echo))
+            .collect { case SessionBytes(_, bytes) => bytes }
+            .scan(ByteString.empty)(_ ++ _)
+            .filter(_.size >= payload.size)
+            .runWith(Sink.head)
+
+          Await.result(received, remainingOrDefault) shouldBe payload
+        }
+
+        // TLS fallback buffers must not enter the shared, fixed-size TCP pool.
+        val pool = pekko.io.Tcp(system).bufferPool

Review Comment:
   This check acquires one buffer from a pool that TLS never used in this run, 
because every buffer took the heap fallback. It never exercises the case where 
a pooled direct buffer is released and replaced by a heap buffer in 
`growTransportOutBuffer`. A regression that releases a grown heap buffer into 
the pool, or releases a pooled buffer twice, would pass. Consider asserting the 
pool contents after the growth test in `TlsGraphStageDeferredCloseSpec` as 
well, e.g. drain the pool and check every buffer is direct with the expected 
capacity and that none are duplicates.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to