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

JingsongLi 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 a9aa06c60b [lance] Close the native and Arrow writers even when the 
final flush fails (#10298)
a9aa06c60b is described below

commit a9aa06c60b35eba8d59bc15dab7ca147b04a0932
Author: ZIHAN DAI <[email protected]>
AuthorDate: Fri Oct 2 11:44:13 2026 +1000

    [lance] Close the native and Arrow writers even when the final flush fails 
(#10298)
---
 .../paimon/format/lance/LanceRecordsWriter.java    | 55 +++++++++++++---
 .../format/lance/LanceRecordsWriterTest.java       | 77 ++++++++++++++++++++++
 2 files changed, 124 insertions(+), 8 deletions(-)

diff --git 
a/paimon-lance/src/main/java/org/apache/paimon/format/lance/LanceRecordsWriter.java
 
b/paimon-lance/src/main/java/org/apache/paimon/format/lance/LanceRecordsWriter.java
index a23d95489f..47b6f60ae8 100644
--- 
a/paimon-lance/src/main/java/org/apache/paimon/format/lance/LanceRecordsWriter.java
+++ 
b/paimon-lance/src/main/java/org/apache/paimon/format/lance/LanceRecordsWriter.java
@@ -95,9 +95,35 @@ public class LanceRecordsWriter implements 
BundleFormatWriter {
 
     @Override
     public void close() throws IOException {
-        flush();
+        Throwable throwable = null;
+
+        try {
+            flush();
+        } catch (Throwable t) {
+            throwable = t;
+        }
+
         LOG.info("Jni cost: " + jniCost + "ms for file: " + 
nativeWriter.path());
-        closeImpl();
+        long t1 = System.currentTimeMillis();
+
+        try {
+            nativeWriter.close();
+        } catch (Throwable t) {
+            throwable = addSuppressed(throwable, t);
+        }
+
+        try {
+            arrowFormatWriter.close();
+        } catch (Throwable t) {
+            throwable = addSuppressed(throwable, t);
+        }
+
+        long closeCost = (System.currentTimeMillis() - t1);
+        LOG.info("Close cost: " + closeCost + "ms for file: " + 
nativeWriter.path());
+
+        if (throwable != null) {
+            rethrow(throwable);
+        }
     }
 
     private void flush() throws IOException {
@@ -110,11 +136,24 @@ public class LanceRecordsWriter implements 
BundleFormatWriter {
         arrowFormatWriter.reset();
     }
 
-    private void closeImpl() throws IOException {
-        long t1 = System.currentTimeMillis();
-        this.nativeWriter.close();
-        this.arrowFormatWriter.close();
-        long closeCost = (System.currentTimeMillis() - t1);
-        LOG.info("Close cost: " + closeCost + "ms for file: " + 
nativeWriter.path());
+    private static Throwable addSuppressed(Throwable throwable, Throwable 
suppressed) {
+        if (throwable == null) {
+            return suppressed;
+        }
+        throwable.addSuppressed(suppressed);
+        return throwable;
+    }
+
+    private static void rethrow(Throwable throwable) throws IOException {
+        if (throwable instanceof IOException) {
+            throw (IOException) throwable;
+        }
+        if (throwable instanceof RuntimeException) {
+            throw (RuntimeException) throwable;
+        }
+        if (throwable instanceof Error) {
+            throw (Error) throwable;
+        }
+        throw new IOException(throwable);
     }
 }
diff --git 
a/paimon-lance/src/test/java/org/apache/paimon/format/lance/LanceRecordsWriterTest.java
 
b/paimon-lance/src/test/java/org/apache/paimon/format/lance/LanceRecordsWriterTest.java
index 7d5161d5dc..c1b8b5f9c8 100644
--- 
a/paimon-lance/src/test/java/org/apache/paimon/format/lance/LanceRecordsWriterTest.java
+++ 
b/paimon-lance/src/test/java/org/apache/paimon/format/lance/LanceRecordsWriterTest.java
@@ -44,6 +44,7 @@ import java.util.UUID;
 import java.util.stream.Collectors;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /** Tests for {@link LanceRecordsWriter}. */
 class LanceRecordsWriterTest {
@@ -209,12 +210,88 @@ class LanceRecordsWriterTest {
         
assertThat(nativeWriter.snapshots.get(0).values.get(0)).containsExactly(10);
     }
 
+    @Test
+    void testCloseContinuesCleanupAfterFlushFailure() {
+        RowType rowType = RowType.builder().field("value", 
DataTypes.INT()).build();
+        RuntimeException flushFailure = new RuntimeException("flush failed");
+        IOException nativeCloseFailure = new IOException("native close 
failed");
+        IllegalStateException arrowCloseFailure = new 
IllegalStateException("arrow close failed");
+        FailingCloseArrowFormatWriter arrowWriter =
+                new FailingCloseArrowFormatWriter(rowType, flushFailure, 
arrowCloseFailure);
+        FailingCloseLanceWriter nativeWriter = new 
FailingCloseLanceWriter(nativeCloseFailure);
+        LanceRecordsWriter writer = new LanceRecordsWriter(() -> 0L, 
arrowWriter, nativeWriter);
+
+        try {
+            assertThatThrownBy(writer::close).isSameAs(flushFailure);
+            assertThat(flushFailure.getSuppressed())
+                    .containsExactly(nativeCloseFailure, arrowCloseFailure);
+            assertThat(nativeWriter.closeCount).isEqualTo(1);
+            assertThat(arrowWriter.closeCount).isEqualTo(1);
+        } finally {
+            if (arrowWriter.closeCount == 0) {
+                arrowWriter.closeWithoutFailure();
+            }
+        }
+    }
+
     private static void setInt(IntVector vector, int value) {
         vector.allocateNew(1);
         vector.setSafe(0, value);
         vector.setValueCount(1);
     }
 
+    private static class FailingCloseArrowFormatWriter extends 
ArrowFormatWriter {
+
+        private final RuntimeException flushFailure;
+        private final RuntimeException closeFailure;
+        private int closeCount;
+
+        private FailingCloseArrowFormatWriter(
+                RowType rowType, RuntimeException flushFailure, 
RuntimeException closeFailure) {
+            super(rowType, 1024, true);
+            this.flushFailure = flushFailure;
+            this.closeFailure = closeFailure;
+        }
+
+        @Override
+        public void flush() {
+            throw flushFailure;
+        }
+
+        @Override
+        public void close() {
+            closeCount++;
+            super.close();
+            throw closeFailure;
+        }
+
+        private void closeWithoutFailure() {
+            super.close();
+        }
+    }
+
+    private static class FailingCloseLanceWriter extends LanceWriter {
+
+        private final IOException closeFailure;
+        private int closeCount;
+
+        private FailingCloseLanceWriter(IOException closeFailure) {
+            super("unused", Collections.emptyMap());
+            this.closeFailure = closeFailure;
+        }
+
+        @Override
+        public void close() throws IOException {
+            closeCount++;
+            throw closeFailure;
+        }
+
+        @Override
+        public String path() {
+            return "unused";
+        }
+    }
+
     private static class CapturingLanceWriter extends LanceWriter {
 
         private final List<Snapshot> snapshots = new ArrayList<>();

Reply via email to