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 d781a1e19f [core] Continue FileStoreWrite cleanup after writer close
failures (#10288)
d781a1e19f is described below
commit d781a1e19fc983b87c85e40d543c6afca43e79ce
Author: ZIHAN DAI <[email protected]>
AuthorDate: Fri Oct 2 11:45:23 2026 +1000
[core] Continue FileStoreWrite cleanup after writer close failures (#10288)
---
.../paimon/operation/BaseAppendFileStoreWrite.java | 8 +-
.../paimon/operation/KeyValueFileStoreWrite.java | 4 +-
.../paimon/operation/MemoryFileStoreWrite.java | 8 +-
.../operation/AbstractFileStoreWriteCloseTest.java | 108 ++++++++++++++++++++-
4 files changed, 115 insertions(+), 13 deletions(-)
diff --git
a/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
b/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
index 2e734552bd..07d15637fe 100644
---
a/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
+++
b/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
@@ -46,6 +46,7 @@ import org.apache.paimon.utils.CommitIncrement;
import org.apache.paimon.utils.ExceptionUtils;
import org.apache.paimon.utils.FileStorePathFactory;
import org.apache.paimon.utils.IOExceptionSupplier;
+import org.apache.paimon.utils.IOUtils;
import org.apache.paimon.utils.LongCounter;
import org.apache.paimon.utils.MutableObjectIterator;
import org.apache.paimon.utils.RecordWriter;
@@ -256,9 +257,10 @@ public abstract class BaseAppendFileStoreWrite extends
MemoryFileStoreWrite<Inte
@Override
public void close() throws Exception {
- super.close();
- if (blobFetchMetrics != null) {
- blobFetchMetrics.close();
+ if (blobFetchMetrics == null) {
+ super.close();
+ } else {
+ IOUtils.closeAll(super::close, blobFetchMetrics::close);
}
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/operation/KeyValueFileStoreWrite.java
b/paimon-core/src/main/java/org/apache/paimon/operation/KeyValueFileStoreWrite.java
index a81516b8e7..b685a152c7 100644
---
a/paimon-core/src/main/java/org/apache/paimon/operation/KeyValueFileStoreWrite.java
+++
b/paimon-core/src/main/java/org/apache/paimon/operation/KeyValueFileStoreWrite.java
@@ -49,6 +49,7 @@ import org.apache.paimon.types.RowType;
import org.apache.paimon.utils.CommitIncrement;
import org.apache.paimon.utils.FieldsComparator;
import org.apache.paimon.utils.FileStorePathFactory;
+import org.apache.paimon.utils.IOUtils;
import org.apache.paimon.utils.SnapshotManager;
import org.apache.paimon.utils.UserDefinedSeqComparator;
@@ -258,7 +259,6 @@ public class KeyValueFileStoreWrite extends
MemoryFileStoreWrite<KeyValue> {
@Override
public void close() throws Exception {
- super.close();
- compactManagerFactory.close();
+ IOUtils.closeAll(super::close, compactManagerFactory::close);
}
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/operation/MemoryFileStoreWrite.java
b/paimon-core/src/main/java/org/apache/paimon/operation/MemoryFileStoreWrite.java
index 96a6d6b483..f6b67fad4d 100644
---
a/paimon-core/src/main/java/org/apache/paimon/operation/MemoryFileStoreWrite.java
+++
b/paimon-core/src/main/java/org/apache/paimon/operation/MemoryFileStoreWrite.java
@@ -30,6 +30,7 @@ import org.apache.paimon.metrics.MetricRegistry;
import org.apache.paimon.operation.metrics.WriterBufferMetric;
import org.apache.paimon.table.sink.CommitMessage;
import org.apache.paimon.types.RowType;
+import org.apache.paimon.utils.IOUtils;
import org.apache.paimon.utils.RecordWriter;
import org.apache.paimon.utils.SnapshotManager;
@@ -161,9 +162,10 @@ public abstract class MemoryFileStoreWrite<T> extends
AbstractFileStoreWrite<T>
@Override
public void close() throws Exception {
- super.close();
- if (this.writerBufferMetric != null) {
- this.writerBufferMetric.close();
+ if (writerBufferMetric == null) {
+ super.close();
+ } else {
+ IOUtils.closeAll(super::close, writerBufferMetric::close);
}
}
}
diff --git
a/paimon-core/src/test/java/org/apache/paimon/operation/AbstractFileStoreWriteCloseTest.java
b/paimon-core/src/test/java/org/apache/paimon/operation/AbstractFileStoreWriteCloseTest.java
index 8c4ac1252f..95125d6908 100644
---
a/paimon-core/src/test/java/org/apache/paimon/operation/AbstractFileStoreWriteCloseTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/operation/AbstractFileStoreWriteCloseTest.java
@@ -18,6 +18,7 @@
package org.apache.paimon.operation;
+import org.apache.paimon.KeyValue;
import org.apache.paimon.catalog.Catalog;
import org.apache.paimon.catalog.FileSystemCatalog;
import org.apache.paimon.catalog.Identifier;
@@ -27,6 +28,11 @@ import org.apache.paimon.data.InternalRow;
import org.apache.paimon.fs.Path;
import org.apache.paimon.fs.local.LocalFileIO;
import org.apache.paimon.io.DataFileMeta;
+import org.apache.paimon.mergetree.compact.KvCompactionManagerFactory;
+import org.apache.paimon.metrics.MetricGroupImpl;
+import org.apache.paimon.metrics.MetricRegistry;
+import org.apache.paimon.operation.metrics.BlobFetchMetrics;
+import org.apache.paimon.operation.metrics.WriterBufferMetric;
import org.apache.paimon.schema.Schema;
import org.apache.paimon.table.FileStoreTable;
import org.apache.paimon.types.DataTypes;
@@ -36,10 +42,13 @@ import org.apache.paimon.utils.RecordWriter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import java.lang.reflect.Field;
+import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -116,6 +125,88 @@ public class AbstractFileStoreWriteCloseTest {
assertThat(write.writers()).isEmpty();
}
+ @Test
+ public void testAppendCloseRunsBlobMetricCleanupAfterWriterFailure()
throws Exception {
+ BucketedAppendFileStoreWrite write = newWrite();
+ RecordingWriter failing = new RecordingWriter("bucket-0", true);
+ putWriters(write, failing);
+
+ AtomicBoolean blobMetricClosed = new AtomicBoolean();
+ MetricRegistry metricRegistry =
+ (groupName, variables) ->
+ new MetricGroupImpl(groupName, variables) {
+ @Override
+ public void close() {
+ if
(BlobFetchMetrics.GROUP_NAME.equals(groupName)) {
+ blobMetricClosed.set(true);
+ }
+ }
+ };
+ Field blobMetricField =
BaseAppendFileStoreWrite.class.getDeclaredField("blobFetchMetrics");
+ blobMetricField.setAccessible(true);
+ blobMetricField.set(write, new BlobFetchMetrics(metricRegistry,
"test"));
+
+ assertThatThrownBy(write::close).hasMessage("close failed in
bucket-0");
+ assertThat(blobMetricClosed).isTrue();
+ }
+
+ @Test
+ public void testKeyValueCloseRunsOuterCleanupAfterWriterFailure() throws
Exception {
+ KeyValueFileStoreWrite write = newKeyValueWrite();
+ AtomicBoolean metricClosed = new AtomicBoolean();
+ MetricRegistry metricRegistry =
+ (groupName, variables) ->
+ new MetricGroupImpl(groupName, variables) {
+ @Override
+ public void close() {
+ if
(WriterBufferMetric.GROUP_NAME.equals(groupName)) {
+ metricClosed.set(true);
+ }
+ }
+ };
+ write.withMetricRegistry(metricRegistry);
+
+ @SuppressWarnings("unchecked")
+ RecordWriter<KeyValue> failingWriter =
+ (RecordWriter<KeyValue>)
+ Proxy.newProxyInstance(
+ RecordWriter.class.getClassLoader(),
+ new Class<?>[] {RecordWriter.class},
+ (proxy, method, args) -> {
+ if ("close".equals(method.getName())) {
+ throw new Exception("writer close
failed");
+ }
+ return null;
+ });
+ HashMap<Integer, AbstractFileStoreWrite.WriterContainer<KeyValue>>
bucketWriters =
+ new HashMap<>();
+ bucketWriters.put(
+ 0,
+ new AbstractFileStoreWrite.WriterContainer<>(
+ failingWriter, 1, null, null, null, null));
+ write.writers().put(partition(0), bucketWriters);
+
+ AtomicBoolean factoryClosed = new AtomicBoolean();
+ KvCompactionManagerFactory compactManagerFactory =
+ (KvCompactionManagerFactory)
+ Proxy.newProxyInstance(
+
KvCompactionManagerFactory.class.getClassLoader(),
+ new Class<?>[]
{KvCompactionManagerFactory.class},
+ (proxy, method, args) -> {
+ if ("close".equals(method.getName())) {
+ factoryClosed.set(true);
+ }
+ return null;
+ });
+ Field factoryField =
KeyValueFileStoreWrite.class.getDeclaredField("compactManagerFactory");
+ factoryField.setAccessible(true);
+ factoryField.set(write, compactManagerFactory);
+
+ assertThatThrownBy(write::close).hasMessage("writer close failed");
+ assertThat(metricClosed).isTrue();
+ assertThat(factoryClosed).isTrue();
+ }
+
private void putWriters(BucketedAppendFileStoreWrite write,
RecordingWriter... writers) {
HashMap<Integer, AbstractFileStoreWrite.WriterContainer<InternalRow>>
bucketWriters =
new HashMap<>();
@@ -129,7 +220,11 @@ public class AbstractFileStoreWriteCloseTest {
}
private BucketedAppendFileStoreWrite newWrite() throws Exception {
- return (BucketedAppendFileStoreWrite)
createFileStoreTable().store().newWrite("ss");
+ return (BucketedAppendFileStoreWrite)
createFileStoreTable(false).store().newWrite("ss");
+ }
+
+ private KeyValueFileStoreWrite newKeyValueWrite() throws Exception {
+ return (KeyValueFileStoreWrite)
createFileStoreTable(true).store().newWrite("ss");
}
private static BinaryRow partition(int i) {
@@ -140,17 +235,20 @@ public class AbstractFileStoreWriteCloseTest {
return binaryRow;
}
- private FileStoreTable createFileStoreTable() throws Exception {
+ private FileStoreTable createFileStoreTable(boolean primaryKey) throws
Exception {
Catalog catalog = new FileSystemCatalog(LocalFileIO.create(), new
Path(tempDir.toString()));
- Schema schema =
+ Schema.Builder schemaBuilder =
Schema.newBuilder()
.column("f0", DataTypes.INT())
.column("f1", DataTypes.INT())
.column("f2", DataTypes.INT())
.partitionKeys("f0")
.option("bucket", "100")
- .option("bucket-key", "f1")
- .build();
+ .option("bucket-key", "f1");
+ if (primaryKey) {
+ schemaBuilder.primaryKey("f0", "f1");
+ }
+ Schema schema = schemaBuilder.build();
Identifier identifier = Identifier.create("default", "test");
catalog.createDatabase("default", false);
catalog.createTable(identifier, schema, false);