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

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git

commit 28cd44bc4fe05bc4c487268c789ced88b76e1c0b
Author: Gary D. Gregory <garydgreg...@gmail.com>
AuthorDate: Mon Mar 17 09:12:40 2025 -0400

    Some ZIP operations won't read all data from a non-blocking file channel
---
 src/changes/changes.xml                                  |  1 +
 .../apache/commons/compress/archivers/zip/ZipIoUtil.java | 16 ++++++++++++----
 .../archivers/zip/FileRandomAccessOutputStreamTest.java  |  6 +++---
 .../zip/SeekableChannelRandomAccessOutputStreamTest.java |  4 ++--
 .../commons/compress/archivers/zip/ZipIoUtilTest.java    |  8 ++++----
 5 files changed, 22 insertions(+), 13 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 000ab3dec..1ef938995 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -69,6 +69,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action type="fix" dev="ggregory" due-to="Gary Gregory">Package-private 
and private classes can be final.</action>
       <action type="fix" dev="ggregory" due-to="Gary Gregory">Deprecate 
ArjArchiveEntry.HostOs.HostOs().</action>
       <action type="fix" dev="sebb">Drop coveralls reference (no longer 
needed)</action>
+      <action type="fix" dev="ggregory" due-to="Gary Gregory">Some ZIP 
operations won't read all data from a non-blocking file channel.</action>
       <!-- ADD -->
       <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
GzipParameters.getModificationInstant().</action>
       <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
GzipParameters.setModificationInstant(Instant).</action>
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/zip/ZipIoUtil.java 
b/src/main/java/org/apache/commons/compress/archivers/zip/ZipIoUtil.java
index 4a2fe0bb0..d09416f71 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipIoUtil.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipIoUtil.java
@@ -42,7 +42,11 @@ static void writeAll(final FileChannel channel, final 
ByteBuffer buffer, final l
         for (long currentPos = position; buffer.hasRemaining();) {
             final int remaining = buffer.remaining();
             final int written = channel.write(buffer, currentPos);
-            if (written <= 0) {
+            if (written == 0) {
+                // A non-blocking channel
+                Thread.yield();
+                continue;
+            } else if (written < 0) {
                 throw new IOException("Failed to write all bytes in the buffer 
for channel=" + channel + ", length=" + remaining + ", written=" + written);
             }
             currentPos += written;
@@ -52,15 +56,19 @@ static void writeAll(final FileChannel channel, final 
ByteBuffer buffer, final l
     /**
      * Writes all bytes in a buffer to a channel.
      *
-     * @param channel  The target channel.
-     * @param buffer   The source bytes.
+     * @param channel The target channel.
+     * @param buffer  The source bytes.
      * @throws IOException If some I/O error occurs or fails or fails to write 
all bytes.
      */
     static void writeAll(final WritableByteChannel channel, final ByteBuffer 
buffer) throws IOException {
         while (buffer.hasRemaining()) {
             final int remaining = buffer.remaining();
             final int written = channel.write(buffer);
-            if (written <= 0) {
+            if (written == 0) {
+                // A non-blocking channel
+                Thread.yield();
+                continue;
+            } else if (written < 0) {
                 throw new IOException("Failed to write all bytes in the buffer 
for channel=" + channel + ", length=" + remaining + ", written=" + written);
             }
         }
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/zip/FileRandomAccessOutputStreamTest.java
 
b/src/test/java/org/apache/commons/compress/archivers/zip/FileRandomAccessOutputStreamTest.java
index 92cc97d54..871122956 100644
--- 
a/src/test/java/org/apache/commons/compress/archivers/zip/FileRandomAccessOutputStreamTest.java
+++ 
b/src/test/java/org/apache/commons/compress/archivers/zip/FileRandomAccessOutputStreamTest.java
@@ -128,14 +128,14 @@ public void 
testWriteFullyAt_whenFullButPartial_thenSucceed() throws IOException
     public void testWriteFullyAt_whenPartial_thenFail() throws IOException {
         final FileChannel channel = mock(FileChannel.class);
         final FileRandomAccessOutputStream stream = new 
FileRandomAccessOutputStream(channel);
-        when(channel.write((ByteBuffer) any(), eq(20L))).thenAnswer(answer -> {
+        when(channel.write((ByteBuffer) any(), eq(20L))).thenAnswer(answer -> 
0).thenAnswer(answer -> {
             ((ByteBuffer) answer.getArgument(0)).position(3);
             return 3;
         });
-        when(channel.write((ByteBuffer) any(), eq(23L))).thenAnswer(answer -> 
0);
+        when(channel.write((ByteBuffer) any(), eq(23L))).thenAnswer(answer -> 
-1);
         assertThrows(IOException.class, () -> 
stream.writeAll("hello".getBytes(StandardCharsets.UTF_8), 20));
 
-        verify(channel, times(1)).write((ByteBuffer) any(), eq(20L));
+        verify(channel, times(2)).write((ByteBuffer) any(), eq(20L));
         verify(channel, times(1)).write((ByteBuffer) any(), eq(23L));
         verify(channel, times(0)).write((ByteBuffer) any(), eq(25L));
 
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/zip/SeekableChannelRandomAccessOutputStreamTest.java
 
b/src/test/java/org/apache/commons/compress/archivers/zip/SeekableChannelRandomAccessOutputStreamTest.java
index 6767483b1..9ca006765 100644
--- 
a/src/test/java/org/apache/commons/compress/archivers/zip/SeekableChannelRandomAccessOutputStreamTest.java
+++ 
b/src/test/java/org/apache/commons/compress/archivers/zip/SeekableChannelRandomAccessOutputStreamTest.java
@@ -135,11 +135,11 @@ public void testWriteFullyAt_whenPartial_thenFail() 
throws IOException {
         when(channel.write((ByteBuffer) any())).thenAnswer(answer -> {
             ((ByteBuffer) answer.getArgument(0)).position(3);
             return 3;
-        }).thenAnswer(answer -> 0);
+        }).thenAnswer(answer -> 0).thenAnswer(answer -> -1);
 
         assertThrows(IOException.class, () -> 
stream.writeAll("hello".getBytes(StandardCharsets.UTF_8), 20));
 
-        verify(channel, times(2)).write((ByteBuffer) any());
+        verify(channel, times(3)).write((ByteBuffer) any());
 
         assertEquals(50L, stream.position());
     }
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/zip/ZipIoUtilTest.java 
b/src/test/java/org/apache/commons/compress/archivers/zip/ZipIoUtilTest.java
index c485826d3..509bb8f46 100644
--- a/src/test/java/org/apache/commons/compress/archivers/zip/ZipIoUtilTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/ZipIoUtilTest.java
@@ -93,9 +93,9 @@ public void testWriteFully_whenPartial_thenFail() throws 
IOException {
             when(channel.write((ByteBuffer) any())).thenAnswer(answer -> {
                 ((ByteBuffer) answer.getArgument(0)).position(3);
                 return 3;
-            }).thenAnswer(answer -> 0);
+            }).thenAnswer(answer -> 0).thenAnswer(answer -> -1);
             assertThrows(IOException.class, () -> ZipIoUtil.writeAll(channel, 
ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8))));
-            verify(channel, times(2)).write((ByteBuffer) any());
+            verify(channel, times(3)).write((ByteBuffer) any());
         }
     }
 
@@ -147,10 +147,10 @@ public void testWriteFullyAt_whenPartial_thenFail() 
throws IOException {
                 ((ByteBuffer) answer.getArgument(0)).position(3);
                 return 3;
             });
-            when(channel.write((ByteBuffer) any(), eq(23L))).thenAnswer(answer 
-> 0);
+            when(channel.write((ByteBuffer) any(), eq(23L))).thenAnswer(answer 
-> 0).thenAnswer(answer -> -1);
             assertThrows(IOException.class, () -> ZipIoUtil.writeAll(channel, 
ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8)), 20));
             verify(channel, times(1)).write((ByteBuffer) any(), eq(20L));
-            verify(channel, times(1)).write((ByteBuffer) any(), eq(23L));
+            verify(channel, times(2)).write((ByteBuffer) any(), eq(23L));
             verify(channel, times(0)).write((ByteBuffer) any(), eq(25L));
         }
     }

Reply via email to