This is an automated email from the ASF dual-hosted git repository.
Zakelly pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git
The following commit(s) were added to refs/heads/master by this push:
new 8fc9538b0dd [FLINK-40952][state/forst] Close the DBOptions copy of
temporary restore DBs (#29430)
8fc9538b0dd is described below
commit 8fc9538b0dd67b76d08a369ba0a28cfed390f5b2
Author: YordanPavlov <[email protected]>
AuthorDate: Fri Oct 9 18:35:02 2026 +0300
[FLINK-40952][state/forst] Close the DBOptions copy of temporary restore
DBs (#29430)
Generated-by: Claude Code (Claude Opus 5.5)
---
.../restore/ForStIncrementalRestoreOperation.java | 30 ++++--
.../ForStIncrementalCheckpointRescalingTest.java | 105 +++++++++++++++++++++
2 files changed, 127 insertions(+), 8 deletions(-)
diff --git
a/flink-state-backends/flink-statebackend-forst/src/main/java/org/apache/flink/state/forst/restore/ForStIncrementalRestoreOperation.java
b/flink-state-backends/flink-statebackend-forst/src/main/java/org/apache/flink/state/forst/restore/ForStIncrementalRestoreOperation.java
index ca3d683a484..a634c36d5a4 100644
---
a/flink-state-backends/flink-statebackend-forst/src/main/java/org/apache/flink/state/forst/restore/ForStIncrementalRestoreOperation.java
+++
b/flink-state-backends/flink-statebackend-forst/src/main/java/org/apache/flink/state/forst/restore/ForStIncrementalRestoreOperation.java
@@ -964,17 +964,24 @@ public class ForStIncrementalRestoreOperation<K>
implements ForStRestoreOperatio
DBOptions dbOptions = new DBOptions(this.forstHandle.getDbOptions());
dbOptions.setDbLogDir("");
- RocksDB restoreDb =
- ForStOperationUtils.openDB(
- dbName,
- columnFamilyDescriptors,
- columnFamilyHandles,
- createColumnFamilyOptions(
-
this.forstHandle.getColumnFamilyOptionsFactory(), "default"),
- dbOptions);
+ RocksDB restoreDb;
+ try {
+ restoreDb =
+ ForStOperationUtils.openDB(
+ dbName,
+ columnFamilyDescriptors,
+ columnFamilyHandles,
+ createColumnFamilyOptions(
+
this.forstHandle.getColumnFamilyOptionsFactory(), "default"),
+ dbOptions);
+ } catch (Exception e) {
+ IOUtils.closeQuietly(dbOptions);
+ throw e;
+ }
return new RestoredDBInstance(
restoreDb,
+ dbOptions,
columnFamilyHandles,
columnFamilyDescriptors,
stateMetaInfoSnapshots,
@@ -995,6 +1002,10 @@ public class ForStIncrementalRestoreOperation<K>
implements ForStRestoreOperatio
@Nonnull final RocksDB db;
+ // A native copy that shares the backend's resources, e.g. the
slot-shared write buffer
+ // manager and block cache with managed memory, so it must be closed
with the DB.
+ @Nonnull final DBOptions dbOptions;
+
@Nonnull final ColumnFamilyHandle defaultColumnFamilyHandle;
@Nonnull final List<ColumnFamilyHandle> columnFamilyHandles;
@@ -1011,12 +1022,14 @@ public class ForStIncrementalRestoreOperation<K>
implements ForStRestoreOperatio
RestoredDBInstance(
@Nonnull RocksDB db,
+ @Nonnull DBOptions dbOptions,
@Nonnull List<ColumnFamilyHandle> columnFamilyHandles,
@Nonnull List<ColumnFamilyDescriptor> columnFamilyDescriptors,
@Nonnull List<StateMetaInfoSnapshot> stateMetaInfoSnapshots,
@Nonnull IncrementalRemoteKeyedStateHandle srcStateHandle,
@Nonnull String instancePath) {
this.db = db;
+ this.dbOptions = dbOptions;
this.defaultColumnFamilyHandle = columnFamilyHandles.remove(0);
this.columnFamilyHandles = columnFamilyHandles;
this.columnFamilyDescriptors = columnFamilyDescriptors;
@@ -1036,6 +1049,7 @@ public class ForStIncrementalRestoreOperation<K>
implements ForStRestoreOperatio
IOUtils.closeQuietly(defaultColumnFamilyHandle);
IOUtils.closeAllQuietly(columnFamilyHandles);
IOUtils.closeQuietly(db);
+ IOUtils.closeQuietly(dbOptions);
IOUtils.closeAllQuietly(columnFamilyOptions);
IOUtils.closeQuietly(readOptions);
}
diff --git
a/flink-state-backends/flink-statebackend-forst/src/test/java/org/apache/flink/state/forst/ForStIncrementalCheckpointRescalingTest.java
b/flink-state-backends/flink-statebackend-forst/src/test/java/org/apache/flink/state/forst/ForStIncrementalCheckpointRescalingTest.java
index 4fb2f3f7e34..a12fc7a1f0e 100644
---
a/flink-state-backends/flink-statebackend-forst/src/test/java/org/apache/flink/state/forst/ForStIncrementalCheckpointRescalingTest.java
+++
b/flink-state-backends/flink-statebackend-forst/src/test/java/org/apache/flink/state/forst/ForStIncrementalCheckpointRescalingTest.java
@@ -24,6 +24,7 @@ import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.configuration.Configuration;
+import org.apache.flink.core.testutils.CommonTestUtils;
import org.apache.flink.runtime.checkpoint.OperatorSubtaskState;
import org.apache.flink.runtime.checkpoint.StateAssignmentOperation;
import org.apache.flink.runtime.state.KeyGroupRange;
@@ -40,13 +41,23 @@ import
org.apache.flink.testutils.junit.extensions.parameterized.ParameterizedTe
import org.apache.flink.testutils.junit.extensions.parameterized.Parameters;
import org.apache.flink.util.Collector;
+import org.forstdb.ColumnFamilyOptions;
+import org.forstdb.DBOptions;
+import org.forstdb.InfoLogLevel;
+import org.forstdb.Logger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
+import javax.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
import java.nio.file.Path;
+import java.time.Duration;
+import java.util.Collection;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import static
org.apache.flink.state.forst.ForStConfigurableOptions.USE_INGEST_DB_RESTORE_MODE;
import static org.assertj.core.api.Assertions.assertThat;
@@ -419,6 +430,70 @@ class ForStIncrementalCheckpointRescalingTest {
}
}
+ /**
+ * A scale-in restore opens temporary DBs with copies of the backend's DB
options. The native
+ * options keep everything set on them alive (here a logger; in production
the shared write
+ * buffer manager and block cache), so every copy must be closed with its
temporary DB.
+ */
+ @TestTemplate
+ void testScalingDownReleasesDBOptions() throws Exception {
+ TrackingLoggerOptionsFactory.LOGGERS.clear();
+
+ scaleDownFromTwoToOne();
+
+ assertThat(TrackingLoggerOptionsFactory.LOGGERS).hasSize(3);
+ CommonTestUtils.waitUtil(
+ () -> {
+ System.gc();
+ return TrackingLoggerOptionsFactory.LOGGERS.stream()
+ .allMatch(logger -> logger.get() == null);
+ },
+ Duration.ofSeconds(30),
+ Duration.ofMillis(100),
+ "The logger of a disposed backend is still referenced by
native DB options.");
+ }
+
+ /** Runs in its own frame so that no harness stays reachable from the
caller. */
+ @SuppressWarnings("unchecked")
+ private void scaleDownFromTwoToOne() throws Exception {
+ KeyedOneInputStreamOperatorTestHarness<String, String, Integer>[]
harness2 =
+ new KeyedOneInputStreamOperatorTestHarness[2];
+ OperatorSubtaskState snapshot2;
+ try {
+ for (int i = 0; i < 2; i++) {
+ harness2[i] = getHarnessTest(keySelector, maxParallelism, 2,
i);
+ harness2[i].setStateBackend(getStateBackend(new
TrackingLoggerOptionsFactory()));
+ harness2[i].setCheckpointStorage(
+ new FileSystemCheckpointStorage("file://" +
rootFolder.toAbsolutePath()));
+ harness2[i].open();
+ }
+ validHarnessResult(
+ harness2[0], 1, records[0], records[1], records[2],
records[3], records[4]);
+ validHarnessResult(
+ harness2[1], 1, records[5], records[6], records[7],
records[8], records[9]);
+ snapshot2 =
+ AbstractStreamOperatorTestHarness.repackageState(
+ harness2[0].snapshot(0, 0),
harness2[1].snapshot(0, 0));
+ } finally {
+ closeHarness(harness2);
+ }
+
+ OperatorSubtaskState initState =
+ AbstractStreamOperatorTestHarness.repartitionOperatorState(
+ snapshot2, maxParallelism, 2, 1, 0);
+ try (KeyedOneInputStreamOperatorTestHarness<String, String, Integer>
harness =
+ getHarnessTest(keySelector, maxParallelism, 1, 0)) {
+ harness.setStateBackend(getStateBackend(new
TrackingLoggerOptionsFactory()));
+ harness.setCheckpointStorage(
+ new FileSystemCheckpointStorage("file://" +
rootFolder.toAbsolutePath()));
+ harness.setup();
+ harness.initializeState(initState);
+ harness.open();
+
+ validHarnessResult(harness, 2, records);
+ }
+ }
+
private void closeHarness(KeyedOneInputStreamOperatorTestHarness<?, ?,
?>[] harnessArr)
throws Exception {
for (KeyedOneInputStreamOperatorTestHarness<?, ?, ?> harness :
harnessArr) {
@@ -458,7 +533,12 @@ class ForStIncrementalCheckpointRescalingTest {
}
private StateBackend getStateBackend() {
+ return getStateBackend(null);
+ }
+
+ private StateBackend getStateBackend(@Nullable ForStOptionsFactory
optionsFactory) {
ForStStateBackend forStStateBackend = new ForStStateBackend();
+ forStStateBackend.setForStOptions(optionsFactory);
Configuration configuration = new Configuration();
configuration.set(USE_INGEST_DB_RESTORE_MODE, useIngestDbRestoreMode);
return forStStateBackend.configure(configuration,
getClass().getClassLoader());
@@ -487,6 +567,31 @@ class ForStIncrementalCheckpointRescalingTest {
}
}
+ /** Sets a tracked logger on the DB options of every backend. */
+ private static class TrackingLoggerOptionsFactory implements
ForStOptionsFactory {
+
+ private static final List<WeakReference<Logger>> LOGGERS = new
CopyOnWriteArrayList<>();
+
+ @Override
+ public DBOptions createDBOptions(
+ DBOptions currentOptions, Collection<AutoCloseable>
handlesToClose) {
+ Logger logger =
+ new Logger(currentOptions) {
+ @Override
+ protected void log(InfoLogLevel infoLogLevel, String
logMsg) {}
+ };
+ handlesToClose.add(logger);
+ LOGGERS.add(new WeakReference<>(logger));
+ return currentOptions.setLogger(logger);
+ }
+
+ @Override
+ public ColumnFamilyOptions createColumnOptions(
+ ColumnFamilyOptions currentOptions, Collection<AutoCloseable>
handlesToClose) {
+ return currentOptions;
+ }
+ }
+
/** A simple key selector for tests. */
private class TestKeySelector implements KeySelector<String, String> {
@Override