From a56531db113e02fb386a4465d263d62980dca84a Mon Sep 17 00:00:00 2001 From: KimDaehyeon Date: Tue, 28 Apr 2026 03:55:21 +0900 Subject: [PATCH] Fix data loss in DataBufferUtils synchronous write Prior to this commit, WritableByteChannelSubscriber.hookOnNext() called iterator.next() exactly once. If a DataBuffer consisted of multiple NIO ByteBuffers (e.g., NettyDataBuffer wrapping a CompositeByteBuf), only the first buffer was written to the channel, and the remaining buffers were silently ignored and lost. This commit adds the missing while (iterator.hasNext()) outer loop to ensure all fragmented buffers exposed by the iterator are completely and safely written to the synchronous channel. Signed-off-by: KimDaehyeon --- .../core/io/buffer/DataBufferUtils.java | 8 ++++--- .../core/io/buffer/DataBufferUtilsTests.java | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java index 6c12d4ed3d86..be9f6f9cc756 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java @@ -1138,9 +1138,11 @@ protected void hookOnSubscribe(Subscription subscription) { protected void hookOnNext(DataBuffer dataBuffer) { try { try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) { - ByteBuffer byteBuffer = iterator.next(); - while (byteBuffer.hasRemaining()) { - this.channel.write(byteBuffer); + while (iterator.hasNext()) { + ByteBuffer byteBuffer = iterator.next(); + while (byteBuffer.hasRemaining()) { + this.channel.write(byteBuffer); + } } } this.sink.next(dataBuffer); diff --git a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferUtilsTests.java b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferUtilsTests.java index 00d95af03a18..35ebdd9c3aeb 100644 --- a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferUtilsTests.java @@ -338,6 +338,27 @@ void writeWritableByteChannel(DataBufferFactory bufferFactory) throws Exception channel.close(); } + @ParameterizedDataBufferAllocatingTest + void writeWritableByteChannelWithJoinedBuffer(DataBufferFactory bufferFactory) throws Exception { + super.bufferFactory = bufferFactory; + + DataBuffer foo = stringBuffer("foo"); + DataBuffer bar = stringBuffer("bar"); + DataBuffer joined = bufferFactory.join(List.of(foo, bar)); + + WritableByteChannel channel = Files.newByteChannel(tempFile, StandardOpenOption.WRITE); + + Flux writeResult = DataBufferUtils.write(Flux.just(joined), channel); + StepVerifier.create(writeResult) + .consumeNextWith(stringConsumer("foobar")) + .verifyComplete(); + + String result = String.join("", Files.readAllLines(tempFile)); + + assertThat(result).isEqualTo("foobar"); + channel.close(); + } + @ParameterizedDataBufferAllocatingTest void writeWritableByteChannelErrorInFlux(DataBufferFactory bufferFactory) throws Exception { super.bufferFactory = bufferFactory;