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));