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 43892581de [flink] Drain the commit coordinator on close so
stop-with-savepoint … (#10740)
43892581de is described below
commit 43892581de1cb39a9fba039e9f5444c1aeb69859
Author: Wenchao Wu <[email protected]>
AuthorDate: Sat Oct 24 09:42:39 2026 +0800
[flink] Drain the commit coordinator on close so stop-with-savepoint …
(#10740)
---
.../CommittingWriteOperatorCoordinator.java | 23 ++++++++++--
.../flink/AppendTableSavepointTagITCase.java | 16 ++-------
.../CommittingWriteOperatorCoordinatorTest.java | 41 ++++++++++++++++++++++
3 files changed, 64 insertions(+), 16 deletions(-)
diff --git
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/CommittingWriteOperatorCoordinator.java
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/CommittingWriteOperatorCoordinator.java
index e792432a31..7df3678965 100644
---
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/CommittingWriteOperatorCoordinator.java
+++
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/CommittingWriteOperatorCoordinator.java
@@ -32,6 +32,7 @@ import org.apache.flink.api.common.typeutils.TypeSerializer;
import org.apache.flink.core.io.SimpleVersionedSerialization;
import org.apache.flink.core.io.SimpleVersionedSerializerTypeSerializerProxy;
import org.apache.flink.runtime.jobgraph.OperatorID;
+import org.apache.flink.runtime.operators.coordination.ComponentClosingUtils;
import org.apache.flink.runtime.operators.coordination.OperatorCoordinator;
import org.apache.flink.runtime.operators.coordination.OperatorEvent;
import
org.apache.flink.runtime.operators.coordination.RecreateOnResetOperatorCoordinator;
@@ -42,6 +43,7 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -75,6 +77,10 @@ public class CommittingWriteOperatorCoordinator implements
OperatorCoordinator {
private static final Logger LOG =
LoggerFactory.getLogger(CommittingWriteOperatorCoordinator.class);
+ // Must stay well below the 60s that RecreateOnResetOperatorCoordinator
gives close(), after
+ // which Flink interrupts the closing thread and fails the job.
+ private static final Duration DRAIN_TIMEOUT = Duration.ofSeconds(30);
+
private final OperatorCoordinator.Context context;
private final Committer.Factory<Committable, ManifestCommittable>
committerFactory;
private final boolean streamingCheckpointEnabled;
@@ -154,10 +160,16 @@ public class CommittingWriteOperatorCoordinator
implements OperatorCoordinator {
@Override
public void close() throws Exception {
- transitionState(State.CLOSED);
- if (commitExecutor != null) {
- commitExecutor.shutdownNow();
+ // Let actions already queued run before closing, e.g. the commit and
savepoint tag queued
+ // by the notifyCheckpointComplete of a stop-with-savepoint right
before the job finishes.
+ // They require RUNNING, so the state changes only after draining.
+ if
(!ComponentClosingUtils.tryShutdownExecutorElegantly(commitExecutor,
DRAIN_TIMEOUT)) {
+ LOG.warn(
+ "Commit executor did not finish queued actions within {}.
A pending commit or"
+ + " savepoint tag may be lost.",
+ DRAIN_TIMEOUT);
}
+ transitionState(State.CLOSED);
if (committer != null) {
committer.close();
committer = null;
@@ -602,6 +614,11 @@ public class CommittingWriteOperatorCoordinator implements
OperatorCoordinator {
return state;
}
+ @VisibleForTesting
+ boolean isCommitExecutorShutdown() {
+ return commitExecutor.isShutdown();
+ }
+
@VisibleForTesting
String getCommitUser() {
return commitUser;
diff --git
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/AppendTableSavepointTagITCase.java
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/AppendTableSavepointTagITCase.java
index 759f5567bf..caa91d5bcb 100644
---
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/AppendTableSavepointTagITCase.java
+++
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/AppendTableSavepointTagITCase.java
@@ -43,7 +43,6 @@ import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.table.data.GenericRowData;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.data.StringData;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@@ -107,19 +106,10 @@ public class AppendTableSavepointTagITCase extends
AbstractTestBase {
* unlike an async savepoint, so the tag is created for the savepoint's
own snapshot rather than
* caught up by a later checkpoint. Both commit paths must still produce
the same tag.
*
- * <p>Disabled for now: on the coordinator-commit path this is racy. The
coordinator creates the
- * tag asynchronously on its single-thread commit executor
(notifyCheckpointComplete ->
- * tagUpTo), but stop-with-savepoint terminates the job right after the
savepoint, and the
- * coordinator's {@code close()} calls {@code
commitExecutor.shutdownNow()}, which can drop the
- * not-yet-run tag task so the tag is silently lost. The classic operator
path is unaffected
- * because it tags synchronously. Re-enable once the coordinator drains
pending commit/tag work
- * on end-of-input shutdown (the follow-up PR that adds proper end-input
handling to the
- * coordinator).
+ * <p>On the coordinator-commit path the tag is created on the commit
executor, which the
+ * coordinator drains on close. That can happen after the
stop-with-savepoint future returns, so
+ * the tag must be polled for.
*/
- // TODO: enable once the coordinator supports end-input handling (drains
pending tag work).
- @Disabled(
- "Coordinator-commit stop-with-savepoint drops the async tag on
shutdownNow; re-enable"
- + " after the coordinator end-input handling PR drains
pending tag work")
@ParameterizedTest(name = "coordinatorCommit = {0}")
@ValueSource(booleans = {true, false})
@Timeout(value = 180, unit = TimeUnit.SECONDS)
diff --git
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/coordinator/CommittingWriteOperatorCoordinatorTest.java
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/coordinator/CommittingWriteOperatorCoordinatorTest.java
index 2357eaccc4..57ac6b66aa 100644
---
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/coordinator/CommittingWriteOperatorCoordinatorTest.java
+++
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/coordinator/CommittingWriteOperatorCoordinatorTest.java
@@ -69,6 +69,7 @@ import java.util.Map;
import java.util.NavigableMap;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -481,6 +482,46 @@ public class CommittingWriteOperatorCoordinatorTest
extends CommitterTestBase {
coordinator.close();
}
+ @Timeout(value = 30, unit = TimeUnit.SECONDS)
+ @Test
+ public void testCloseDrainsQueuedSavepointTag() throws Exception {
+ FileStoreTable table = createUnawareBucketTable();
+ TestingContext context = new TestingContext(new OperatorID(), 1);
+ CommittingWriteOperatorCoordinator coordinator =
+ createCoordinatorWithAutoTag(table, context);
+ coordinator.start();
+ coordinator.waitProcessAllActions();
+
+ // Hold the commit executor so the commit and tag of a
stop-with-savepoint are still
+ // queued when close() starts.
+ CountDownLatch release = new CountDownLatch(1);
+ coordinator.runInEventLoop(release::await, "blocking");
+ coordinator.handleEventFromOperator(
+ 0, 0, savepointEvent(1L,
Collections.singletonList(committable(table, 1, 1))));
+ coordinator.notifyCheckpointComplete(1L);
+
+ CompletableFuture<Void> closed =
+ CompletableFuture.runAsync(
+ () -> {
+ try {
+ coordinator.close();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ // Release the executor only once close() has shut it down.
+ while (!coordinator.isCommitExecutorShutdown()) {
+ Thread.sleep(10);
+ }
+ release.countDown();
+ closed.get();
+
+ assertThat(coordinator.getCurrentState())
+ .isEqualTo(CommittingWriteOperatorCoordinator.State.CLOSED);
+
assertThat(table.snapshotManager().latestSnapshot().commitIdentifier()).isEqualTo(1L);
+ assertThat(table.tagManager().tagExists(savepointTag(1L))).isTrue();
+ }
+
private static String savepointTag(long checkpointId) {
return SavepointTagUtils.tagNameOf(checkpointId);
}