This is an automated email from the ASF dual-hosted git repository.

tomaswolf pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git


The following commit(s) were added to refs/heads/master by this push:
     new 7436b1c75 GH-656: shrink ChannelPipedInputStream buffer after all data 
is read
7436b1c75 is described below

commit 7436b1c75f243e3193abaab42537ef463df2dfe2
Author: arimu1 <[email protected]>
AuthorDate: Mon Aug 24 18:22:47 2026 +0700

    GH-656: shrink ChannelPipedInputStream buffer after all data is read
    
    Replace compact() with a fresh default-sized ByteArrayBuffer when the
    pipe is drained, including on EOF with no pending data, so cached SSH
    sessions do not retain large backing arrays.
---
 .../common/channel/ChannelPipedInputStream.java    |  7 ++++-
 .../channel/ChannelPipedInputStreamTest.java       | 36 ++++++++++++++++++++++
 2 files changed, 42 insertions(+), 1 deletion(-)

diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
index 35397295d..0088f8da0 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
@@ -153,7 +153,9 @@ public class ChannelPipedInputStream extends InputStream 
implements ChannelPiped
                 len = buffer.available();
             }
             buffer.getRawBytes(b, off, len);
-            if ((buffer.rpos() > localWindow.getPacketSize()) || 
(buffer.available() == 0)) {
+            if (buffer.available() == 0) {
+                buffer = new ByteArrayBuffer();
+            } else if (buffer.rpos() > localWindow.getPacketSize()) {
                 buffer.compact();
             }
         } finally {
@@ -170,6 +172,9 @@ public class ChannelPipedInputStream extends InputStream 
implements ChannelPiped
         lock.lock();
         try {
             writerClosed.set(true);
+            if (buffer != null && buffer.available() == 0) {
+                buffer = new ByteArrayBuffer();
+            }
             dataAvailable.signalAll();
         } finally {
             lock.unlock();
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedInputStreamTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedInputStreamTest.java
index 30f41eca4..5e66c48df 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedInputStreamTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedInputStreamTest.java
@@ -19,11 +19,13 @@
 package org.apache.sshd.common.channel;
 
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collections;
 
 import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.util.test.BaseTestSupport;
 import org.apache.sshd.util.test.BogusChannel;
 import org.junit.jupiter.api.MethodOrderer.MethodName;
@@ -60,6 +62,34 @@ public class ChannelPipedInputStreamTest extends 
BaseTestSupport {
         }
     }
 
+    @Test
+    void bufferShrinksAfterAllDataRead() throws Exception {
+        try (ChannelPipedInputStream stream = createTestStream()) {
+            int dataLen = 64 * 1024;
+            byte[] data = new byte[dataLen];
+            Arrays.fill(data, (byte) 'x');
+            stream.receive(data, 0, data.length);
+            stream.eof();
+
+            ByteArrayBuffer buffer = getInternalBuffer(stream);
+            assertEquals(dataLen, buffer.array().length, "Buffer should grow 
to hold received data");
+
+            byte[] readBuf = new byte[dataLen];
+            int totalRead = 0;
+            while (totalRead < dataLen) {
+                int n = stream.read(readBuf, totalRead, dataLen - totalRead);
+                if (n < 0) {
+                    fail("Unexpected EOF before all data read");
+                }
+                totalRead += n;
+            }
+            assertEquals(-1, stream.read(), "Unexpectedly not at EOF");
+
+            assertEquals(ByteArrayBuffer.DEFAULT_SIZE, 
getInternalBuffer(stream).array().length,
+                    "Buffer should shrink after all data has been read");
+        }
+    }
+
     @Test
     void idempotentClose() throws IOException {
         try (ChannelPipedInputStream stream = createTestStream()) {
@@ -80,6 +110,12 @@ public class ChannelPipedInputStreamTest extends 
BaseTestSupport {
         return new ChannelPipedInputStream(channel, window);
     }
 
+    private static ByteArrayBuffer getInternalBuffer(ChannelPipedInputStream 
stream) throws Exception {
+        Field f = ChannelPipedInputStream.class.getDeclaredField("buffer");
+        f.setAccessible(true);
+        return (ByteArrayBuffer) f.get(stream);
+    }
+
     private static void assertStreamEquals(byte[] expected, byte[] read) {
         if (expected.length > read.length) {
             fail("Less bytes than expected: " + Arrays.toString(expected) + " 
but got: " + Arrays.toString(read));

Reply via email to