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

lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new dd386bafb [avro] Unwrap AvroRuntimeException and throw the real 
IOException (#4179)
dd386bafb is described below

commit dd386bafbd6b1dfd648964dd3a78563acc3619b1
Author: tsreaper <[email protected]>
AuthorDate: Thu Sep 12 16:12:21 2024 +0800

    [avro] Unwrap AvroRuntimeException and throw the real IOException (#4179)
---
 .../paimon/utils/IteratorResultIterator.java       |  8 +--
 .../apache/paimon/utils/IteratorWithException.java | 27 ++++++++
 .../apache/paimon/format/avro/AvroBulkFormat.java  | 38 +++++++----
 .../paimon/format/avro/AvroFileFormatTest.java     | 74 +++++++++++++++++++++-
 4 files changed, 127 insertions(+), 20 deletions(-)

diff --git 
a/paimon-common/src/main/java/org/apache/paimon/utils/IteratorResultIterator.java
 
b/paimon-common/src/main/java/org/apache/paimon/utils/IteratorResultIterator.java
index 579157d44..57e6c4f6f 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/utils/IteratorResultIterator.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/utils/IteratorResultIterator.java
@@ -25,18 +25,18 @@ import org.apache.paimon.reader.RecordReader;
 
 import javax.annotation.Nullable;
 
-import java.util.Iterator;
+import java.io.IOException;
 
 /** A simple {@link RecordReader.RecordIterator} that returns the elements of 
an iterator. */
 public final class IteratorResultIterator extends 
RecyclableIterator<InternalRow>
         implements FileRecordIterator<InternalRow> {
 
-    private final Iterator<InternalRow> records;
+    private final IteratorWithException<InternalRow, IOException> records;
     private final Path filePath;
     private long nextFilePos;
 
     public IteratorResultIterator(
-            final Iterator<InternalRow> records,
+            final IteratorWithException<InternalRow, IOException> records,
             final @Nullable Runnable recycler,
             final Path filePath,
             long pos) {
@@ -48,7 +48,7 @@ public final class IteratorResultIterator extends 
RecyclableIterator<InternalRow
 
     @Nullable
     @Override
-    public InternalRow next() {
+    public InternalRow next() throws IOException {
         if (records.hasNext()) {
             nextFilePos++;
             return records.next();
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/utils/IteratorWithException.java
 
b/paimon-common/src/main/java/org/apache/paimon/utils/IteratorWithException.java
new file mode 100644
index 000000000..c34c2889d
--- /dev/null
+++ 
b/paimon-common/src/main/java/org/apache/paimon/utils/IteratorWithException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.utils;
+
+/** An iterator which might throws exception. */
+public interface IteratorWithException<V, E extends Exception> {
+
+    boolean hasNext() throws E;
+
+    V next() throws E;
+}
diff --git 
a/paimon-format/src/main/java/org/apache/paimon/format/avro/AvroBulkFormat.java 
b/paimon-format/src/main/java/org/apache/paimon/format/avro/AvroBulkFormat.java
index eda5650b3..7f3e27518 100644
--- 
a/paimon-format/src/main/java/org/apache/paimon/format/avro/AvroBulkFormat.java
+++ 
b/paimon-format/src/main/java/org/apache/paimon/format/avro/AvroBulkFormat.java
@@ -26,8 +26,10 @@ import org.apache.paimon.reader.RecordReader;
 import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.IOUtils;
 import org.apache.paimon.utils.IteratorResultIterator;
+import org.apache.paimon.utils.IteratorWithException;
 import org.apache.paimon.utils.Pool;
 
+import org.apache.avro.AvroRuntimeException;
 import org.apache.avro.file.DataFileReader;
 import org.apache.avro.file.SeekableInput;
 import org.apache.avro.io.DatumReader;
@@ -35,7 +37,7 @@ import org.apache.avro.io.DatumReader;
 import javax.annotation.Nullable;
 
 import java.io.IOException;
-import java.util.Iterator;
+import java.util.function.Supplier;
 
 /** Provides a {@link FormatReaderFactory} for Avro records. */
 public class AvroBulkFormat implements FormatReaderFactory {
@@ -105,7 +107,8 @@ public class AvroBulkFormat implements FormatReaderFactory {
 
             long rowPosition = currentRowPosition;
             currentRowPosition += reader.getBlockCount();
-            Iterator<InternalRow> iterator = new 
AvroBlockIterator(reader.getBlockCount(), reader);
+            IteratorWithException<InternalRow, IOException> iterator =
+                    new AvroBlockIterator(reader.getBlockCount(), reader);
             return new IteratorResultIterator(
                     iterator, () -> pool.recycler().recycle(ticket), filePath, 
rowPosition);
         }
@@ -113,7 +116,7 @@ public class AvroBulkFormat implements FormatReaderFactory {
         private boolean readNextBlock() throws IOException {
             // read the next block with reader,
             // returns true if a block is read and false if we reach the end 
of this split
-            return reader.hasNext() && !reader.pastSync(end);
+            return replaceAvroRuntimeException(reader::hasNext) && 
!reader.pastSync(end);
         }
 
         @Override
@@ -122,7 +125,8 @@ public class AvroBulkFormat implements FormatReaderFactory {
         }
     }
 
-    private static class AvroBlockIterator implements Iterator<InternalRow> {
+    private static class AvroBlockIterator
+            implements IteratorWithException<InternalRow, IOException> {
 
         private long numRecordsRemaining;
         private final DataFileReader<InternalRow> reader;
@@ -138,17 +142,23 @@ public class AvroBulkFormat implements 
FormatReaderFactory {
         }
 
         @Override
-        public InternalRow next() {
-            try {
-                numRecordsRemaining--;
-                // reader.next merely deserialize bytes in memory to java 
objects
-                // and will not read from file
-                // Do not reuse object, manifest file assumes no object reuse
-                return reader.next(null);
-            } catch (IOException e) {
-                throw new RuntimeException(
-                        "Encountered exception when reading from avro format 
file", e);
+        public InternalRow next() throws IOException {
+            numRecordsRemaining--;
+            // reader.next merely deserialize bytes in memory to java objects
+            // and will not read from file
+            // Do not reuse object, manifest file assumes no object reuse
+            return replaceAvroRuntimeException(reader::next);
+        }
+    }
+
+    private static <T> T replaceAvroRuntimeException(Supplier<T> supplier) 
throws IOException {
+        try {
+            return supplier.get();
+        } catch (AvroRuntimeException e) {
+            if (e.getCause() != null && e.getCause() instanceof IOException) {
+                throw (IOException) e.getCause();
             }
+            throw e;
         }
     }
 }
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/avro/AvroFileFormatTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/avro/AvroFileFormatTest.java
index bdf76649e..3f6486baa 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/avro/AvroFileFormatTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/avro/AvroFileFormatTest.java
@@ -24,8 +24,10 @@ import org.apache.paimon.format.FileFormat;
 import org.apache.paimon.format.FileFormatFactory.FormatContext;
 import org.apache.paimon.format.FormatReaderContext;
 import org.apache.paimon.format.FormatWriter;
+import org.apache.paimon.fs.FileIO;
 import org.apache.paimon.fs.Path;
 import org.apache.paimon.fs.PositionOutputStream;
+import org.apache.paimon.fs.SeekableInputStream;
 import org.apache.paimon.fs.local.LocalFileIO;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.reader.RecordReader;
@@ -37,11 +39,15 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
+import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /** Test for avro file format. */
 public class AvroFileFormatTest {
@@ -122,10 +128,74 @@ public class AvroFileFormatTest {
         try (RecordReader<InternalRow> reader =
                 format.createReaderFactory(rowType)
                         .createReader(
-                                new FormatReaderContext(
-                                        fileIO, file, 
fileIO.getFileSize(file))); ) {
+                                new FormatReaderContext(fileIO, file, 
fileIO.getFileSize(file)))) {
             reader.forEachRemainingWithPosition(
                     (rowPosition, row) -> assertThat(row.getInt(0) == 
rowPosition).isTrue());
         }
     }
+
+    @Test
+    void testGetRealIOException() throws IOException {
+        RowType rowType = DataTypes.ROW(DataTypes.INT().notNull());
+        FileFormat format = new AvroFileFormat(new FormatContext(new 
Options(), 16, 16));
+
+        LocalFileIO localFileIO = LocalFileIO.create();
+        Path file = new Path(new Path(tempPath.toUri()), 
UUID.randomUUID().toString());
+        try (PositionOutputStream out = localFileIO.newOutputStream(file, 
false)) {
+            FormatWriter writer = 
format.createWriterFactory(rowType).create(out, "zstd");
+            ThreadLocalRandom random = ThreadLocalRandom.current();
+            // magic number tested by hand
+            for (int i = 0; i < 100000; i++) {
+                writer.addElement(GenericRow.of(random.nextInt()));
+            }
+            writer.close();
+        }
+
+        FileIO failingFileIO =
+                new LocalFileIO() {
+
+                    @Override
+                    public SeekableInputStream newInputStream(Path path) 
throws IOException {
+                        return new FailingInputStream(toFile(path));
+                    }
+
+                    class FailingInputStream extends 
LocalFileIO.LocalSeekableInputStream {
+
+                        private int cnt;
+
+                        public FailingInputStream(File file) throws 
FileNotFoundException {
+                            super(file);
+                            cnt = 0;
+                        }
+
+                        @Override
+                        public int read() throws IOException {
+                            checkException();
+                            return super.read();
+                        }
+
+                        @Override
+                        public int read(byte[] b, int off, int len) throws 
IOException {
+                            checkException();
+                            return super.read(b, off, len);
+                        }
+
+                        private void checkException() throws IOException {
+                            cnt++;
+                            // magic number tested by hand
+                            if (cnt == 200) {
+                                throw new IOException("Artificial exception");
+                            }
+                        }
+                    }
+                };
+        RecordReader<InternalRow> reader =
+                format.createReaderFactory(rowType)
+                        .createReader(
+                                new FormatReaderContext(
+                                        failingFileIO, file, 
failingFileIO.getFileSize(file)));
+        assertThatThrownBy(() -> reader.forEachRemaining(row -> {}))
+                .isInstanceOf(IOException.class)
+                .hasMessageContaining("Artificial exception");
+    }
 }

Reply via email to