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

rkhachatryan 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 d2652156875 [FLINK-40200][Checkpoint] Checkpoint sync phase timeout
d2652156875 is described below

commit d2652156875b26eeb93ec08ff5c9204bcba78112
Author: Efrat Levitan <[email protected]>
AuthorDate: Thu Jul 16 14:25:47 2026 +0300

    [FLINK-40200][Checkpoint] Checkpoint sync phase timeout
    
    For TaskManagers locking up in rocksDB native calls during checkpoint sync 
phase,
    the best mitigation would be to cancel the task and restart.
    A lenient checkpoint timeout X 
{execution.checkpointing.tolerable-failed-checkpoints}
    means it can take days for JM to restart the job out of the deadlock.
    Setting checkpoint sync timeout, after which a task failure is triggered,
    will address thread bloackages faster and without manual intervention.
---
 .../generated/checkpointing_configuration.html     |   6 +
 .../flink/configuration/CheckpointingOptions.java  |  13 +++
 .../org/apache/flink/runtime/taskmanager/Task.java |   9 +-
 .../api/environment/CheckpointConfig.java          |  14 +++
 .../tasks/SubtaskCheckpointCoordinatorImpl.java    |  72 ++++++++++++
 .../tasks/SubtaskCheckpointCoordinatorTest.java    | 126 +++++++++++++++++++++
 .../streaming/util/TestStreamEnvironment.java      |   5 +
 .../CheckpointSyncPhaseTimeoutITCase.java          |  93 +++++++++++++++
 8 files changed, 335 insertions(+), 3 deletions(-)

diff --git a/docs/layouts/shortcodes/generated/checkpointing_configuration.html 
b/docs/layouts/shortcodes/generated/checkpointing_configuration.html
index 2fcbc5fc1ed..c256e4a66e6 100644
--- a/docs/layouts/shortcodes/generated/checkpointing_configuration.html
+++ b/docs/layouts/shortcodes/generated/checkpointing_configuration.html
@@ -152,6 +152,12 @@
             <td>String</td>
             <td>The checkpoint storage implementation to be used to checkpoint 
state.<br />The implementation can be specified either via their shortcut  
name, or via the class name of a <code 
class="highlighter-rouge">CheckpointStorageFactory</code>. If a factory is 
specified it is instantiated via its zero argument constructor and its <code 
class="highlighter-rouge">CheckpointStorageFactory#createFromConfig(ReadableConfig,
 ClassLoader)</code>  method is called.<br />Recognized shortcut [...]
         </tr>
+        <tr>
+            <td><h5>execution.checkpointing.sync-phase-timeout</h5></td>
+            <td style="word-wrap: break-word;">0 ms</td>
+            <td>Duration</td>
+            <td>A timeout for the synchronous phase of a checkpoint, after 
which a task cancellation is triggered. If the task thread is merely slow, the 
task will restart and continue. If a true thread blockage is encountered, (e.g. 
by a blocking native call),  a fatal error will be thrown after the 
task.cancellation.timeoutand trigger a TM restart to address the blockage. 
Defaults to 0 (disabled).</td>
+        </tr>
         <tr>
             <td><h5>execution.checkpointing.timeout</h5></td>
             <td style="word-wrap: break-word;">10 min</td>
diff --git 
a/flink-core/src/main/java/org/apache/flink/configuration/CheckpointingOptions.java
 
b/flink-core/src/main/java/org/apache/flink/configuration/CheckpointingOptions.java
index 75abd4178a0..41397521f84 100644
--- 
a/flink-core/src/main/java/org/apache/flink/configuration/CheckpointingOptions.java
+++ 
b/flink-core/src/main/java/org/apache/flink/configuration/CheckpointingOptions.java
@@ -583,6 +583,19 @@ public class CheckpointingOptions {
                                                     + "will timeout and 
checkpoint barrier will start working as unaligned checkpoint.")
                                     .build());
 
+    public static final ConfigOption<Duration> 
CHECKPOINTING_SYNC_PHASE_TIMEOUT =
+            ConfigOptions.key("execution.checkpointing.sync-phase-timeout")
+                    .durationType()
+                    .defaultValue(Duration.ofSeconds(0L))
+                    .withDescription(
+                            "A timeout for the synchronous phase of a 
checkpoint, after which a task cancellation is triggered."
+                                    + " If the task thread is merely slow, the 
task will restart and continue."
+                                    + " If a true thread blockage is 
encountered, (e.g. by a blocking native call), "
+                                    + " a fatal error will be thrown after the 
"
+                                    + 
TaskManagerOptions.TASK_CANCELLATION_TIMEOUT.key()
+                                    + "and trigger a TM restart to address the 
blockage."
+                                    + " Defaults to 0 (disabled).");
+
     public static final ConfigOption<Boolean> FORCE_UNALIGNED =
             ConfigOptions.key("execution.checkpointing.unaligned.forced")
                     .booleanType()
diff --git 
a/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/Task.java 
b/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/Task.java
index 249f29af1be..3ef341c1372 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/Task.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/Task.java
@@ -1879,20 +1879,23 @@ public class Task
         }
     }
 
-    public static void logTaskThreadStackTrace(
-            Thread thread, String taskName, long timeoutMs, String action) {
+    public static StringBuilder getTaskThreadStackTrace(Thread thread) {
         StackTraceElement[] stack = thread.getStackTrace();
         StringBuilder stackTraceStr = new StringBuilder();
         for (StackTraceElement e : stack) {
             stackTraceStr.append(e).append('\n');
         }
+        return stackTraceStr;
+    }
 
+    public static void logTaskThreadStackTrace(
+            Thread thread, String taskName, long timeoutMs, String action) {
         LOG.warn(
                 "Task '{}' did not react to cancelling signal - {}; it is 
stuck for {} seconds in method:\n {}",
                 taskName,
                 action,
                 timeoutMs / 1000,
-                stackTraceStr);
+                getTaskThreadStackTrace(thread));
     }
 
     /** Various operation of notify checkpoint. */
diff --git 
a/flink-runtime/src/main/java/org/apache/flink/streaming/api/environment/CheckpointConfig.java
 
b/flink-runtime/src/main/java/org/apache/flink/streaming/api/environment/CheckpointConfig.java
index 8d0982a772d..88d47fd245d 100644
--- 
a/flink-runtime/src/main/java/org/apache/flink/streaming/api/environment/CheckpointConfig.java
+++ 
b/flink-runtime/src/main/java/org/apache/flink/streaming/api/environment/CheckpointConfig.java
@@ -498,6 +498,17 @@ public class CheckpointConfig implements 
java.io.Serializable {
                 CheckpointingOptions.ALIGNED_CHECKPOINT_TIMEOUT, 
alignedCheckpointTimeout);
     }
 
+    @Experimental
+    public Duration getCheckpointSyncPhaseTimeout() {
+        return 
configuration.get(CheckpointingOptions.CHECKPOINTING_SYNC_PHASE_TIMEOUT);
+    }
+
+    @Experimental
+    public void setCheckpointSyncPhaseTimeout(Duration 
checkpointSyncPhaseTimeout) {
+        configuration.set(
+                CheckpointingOptions.CHECKPOINTING_SYNC_PHASE_TIMEOUT, 
checkpointSyncPhaseTimeout);
+    }
+
     /**
      * @return the number of subtasks to share the same channel state file, as 
configured via {@link
      *     #setMaxSubtasksPerChannelStateFile(int)} or {@link
@@ -643,6 +654,9 @@ public class CheckpointConfig implements 
java.io.Serializable {
         configuration
                 
.getOptional(CheckpointingOptions.PAUSE_SOURCES_UNTIL_FIRST_CHECKPOINT)
                 .ifPresent(this::setPauseSourcesUntilFirstCheckpoint);
+        configuration
+                
.getOptional(CheckpointingOptions.CHECKPOINTING_SYNC_PHASE_TIMEOUT)
+                .ifPresent(this::setCheckpointSyncPhaseTimeout);
     }
 
     public void setPauseSourcesUntilFirstCheckpoint(boolean 
pauseCheckpointsIfTasksNotRunning) {
diff --git 
a/flink-runtime/src/main/java/org/apache/flink/streaming/runtime/tasks/SubtaskCheckpointCoordinatorImpl.java
 
b/flink-runtime/src/main/java/org/apache/flink/streaming/runtime/tasks/SubtaskCheckpointCoordinatorImpl.java
index 811354c47f3..d4debba167c 100644
--- 
a/flink-runtime/src/main/java/org/apache/flink/streaming/runtime/tasks/SubtaskCheckpointCoordinatorImpl.java
+++ 
b/flink-runtime/src/main/java/org/apache/flink/streaming/runtime/tasks/SubtaskCheckpointCoordinatorImpl.java
@@ -18,6 +18,7 @@
 package org.apache.flink.streaming.runtime.tasks;
 
 import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.CheckpointingOptions;
 import org.apache.flink.runtime.checkpoint.CheckpointException;
 import org.apache.flink.runtime.checkpoint.CheckpointMetaData;
 import org.apache.flink.runtime.checkpoint.CheckpointMetricsBuilder;
@@ -37,6 +38,7 @@ import 
org.apache.flink.runtime.state.CheckpointStorageWorkerView;
 import org.apache.flink.runtime.state.CheckpointStreamFactory;
 import 
org.apache.flink.runtime.state.filesystem.FsMergingCheckpointStorageLocation;
 import org.apache.flink.runtime.taskmanager.AsyncExceptionHandler;
+import org.apache.flink.runtime.taskmanager.AsynchronousException;
 import org.apache.flink.runtime.taskmanager.Task;
 import org.apache.flink.streaming.api.operators.OperatorSnapshotFutures;
 import 
org.apache.flink.streaming.runtime.io.checkpointing.BarrierAlignmentUtil;
@@ -44,6 +46,7 @@ import 
org.apache.flink.streaming.runtime.io.checkpointing.BarrierAlignmentUtil.
 import 
org.apache.flink.streaming.runtime.io.checkpointing.BarrierAlignmentUtil.DelayableTimer;
 import org.apache.flink.util.CollectionUtil;
 import org.apache.flink.util.ExceptionUtils;
+import org.apache.flink.util.FatalExitExceptionHandler;
 import org.apache.flink.util.FlinkRuntimeException;
 import org.apache.flink.util.IOUtils;
 import org.apache.flink.util.clock.Clock;
@@ -70,7 +73,10 @@ import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -132,6 +138,8 @@ class SubtaskCheckpointCoordinatorImpl implements 
SubtaskCheckpointCoordinator {
 
     @Nullable private final FileMergingSnapshotManager 
fileMergingSnapshotManager;
 
+    private final long syncPhaseTimeoutMillis;
+
     @VisibleForTesting
     SubtaskCheckpointCoordinatorImpl(
             CheckpointStorageWorkerView checkpointStorage,
@@ -197,6 +205,16 @@ class SubtaskCheckpointCoordinatorImpl implements 
SubtaskCheckpointCoordinator {
         this.registerTimer = registerTimer;
         this.clock = SystemClock.getInstance();
         this.fileMergingSnapshotManager = fileMergingSnapshotManager;
+        this.syncPhaseTimeoutMillis =
+                env.getJobConfiguration()
+                        
.get(CheckpointingOptions.CHECKPOINTING_SYNC_PHASE_TIMEOUT)
+                        .toMillis();
+        if (syncPhaseTimeoutMillis > 0) {
+            LOG.info(
+                    "Checkpoint synchronous phase timeout for task {} is 
enabled with a timeout of {} ms.",
+                    taskName,
+                    syncPhaseTimeoutMillis);
+        }
     }
 
     public static ChannelStateWriter openChannelStateWriter(
@@ -747,6 +765,11 @@ class SubtaskCheckpointCoordinatorImpl implements 
SubtaskCheckpointCoordinator {
                         checkpointId, checkpointOptions.getTargetLocation());
         storage = applyFileMergingCheckpoint(storage, checkpointOptions);
 
+        CountDownLatch syncPhaseCompleted = new CountDownLatch(1);
+        if (syncPhaseTimeoutMillis > 0) {
+            startSyncPhaseTimeoutWatchdog(checkpointId, 
syncPhaseTimeoutMillis, syncPhaseCompleted);
+        }
+
         try {
             operatorChain.snapshotState(
                     operatorSnapshotsInProgress,
@@ -757,6 +780,7 @@ class SubtaskCheckpointCoordinatorImpl implements 
SubtaskCheckpointCoordinator {
                     storage);
 
         } finally {
+            syncPhaseCompleted.countDown();
             checkpointStorage.clearCacheFor(checkpointId);
         }
 
@@ -849,4 +873,52 @@ class SubtaskCheckpointCoordinatorImpl implements 
SubtaskCheckpointCoordinator {
                     delay);
         }
     }
+
+    private void startSyncPhaseTimeoutWatchdog(
+            long checkpointId, long syncPhaseTimeoutMillis, CountDownLatch 
syncPhaseCompleted) {
+        final Thread taskThread = Thread.currentThread();
+        final Thread syncPhaseTimeoutWatchDog =
+                new Thread(
+                        taskThread.getThreadGroup(),
+                        () -> {
+                            boolean completedInTime;
+                            try {
+                                completedInTime =
+                                        syncPhaseCompleted.await(
+                                                syncPhaseTimeoutMillis, 
TimeUnit.MILLISECONDS);
+                            } catch (InterruptedException e) {
+                                // task shutdown
+                                return;
+                            }
+                            if (completedInTime) {
+                                return;
+                            }
+                            try {
+                                final String errorMessage =
+                                        String.format(
+                                                "Task %s did not complete the 
synchronous phase of checkpoint %s within %s ms.",
+                                                taskName, checkpointId, 
syncPhaseTimeoutMillis);
+                                if (LOG.isWarnEnabled()) {
+                                    LOG.warn(
+                                            errorMessage
+                                                    + "\n"
+                                                    + 
Task.getTaskThreadStackTrace(taskThread));
+                                }
+                                env.failExternally(
+                                        new AsynchronousException(
+                                                new 
TimeoutException(errorMessage)));
+                            } catch (Exception e) {
+                                LOG.error(
+                                        "Error handling sync phase timeout for 
checkpoint {}",
+                                        checkpointId,
+                                        e);
+                            }
+                        },
+                        String.format(
+                                "checkpoint %s sync phase watchdog for %s_%s",
+                                checkpointId, taskName, 
env.getTaskInfo().getIndexOfThisSubtask()));
+        syncPhaseTimeoutWatchDog.setDaemon(true);
+        
syncPhaseTimeoutWatchDog.setUncaughtExceptionHandler(FatalExitExceptionHandler.INSTANCE);
+        syncPhaseTimeoutWatchDog.start();
+    }
 }
diff --git 
a/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/SubtaskCheckpointCoordinatorTest.java
 
b/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/SubtaskCheckpointCoordinatorTest.java
index 6c161f139f6..574091204ef 100644
--- 
a/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/SubtaskCheckpointCoordinatorTest.java
+++ 
b/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/SubtaskCheckpointCoordinatorTest.java
@@ -18,9 +18,12 @@
 
 package org.apache.flink.streaming.runtime.tasks;
 
+import org.apache.flink.api.common.ExecutionConfig;
 import org.apache.flink.api.common.functions.MapFunction;
 import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
 import org.apache.flink.api.common.typeutils.base.StringSerializer;
+import org.apache.flink.configuration.CheckpointingOptions;
+import org.apache.flink.configuration.Configuration;
 import org.apache.flink.core.execution.SavepointFormatType;
 import org.apache.flink.core.testutils.OneShotLatch;
 import org.apache.flink.metrics.groups.OperatorMetricGroup;
@@ -73,6 +76,7 @@ import org.apache.flink.util.ExceptionUtils;
 import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.concurrent.CancellationException;
@@ -82,6 +86,7 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.RunnableFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
 
@@ -95,6 +100,7 @@ import static org.assertj.core.api.Assertions.fail;
 /** Tests for {@link SubtaskCheckpointCoordinator}. */
 class SubtaskCheckpointCoordinatorTest {
     private static final CheckpointStorage CHECKPOINT_STORAGE = new 
JobManagerCheckpointStorage();
+    private static final long DEFAULT_SYNC_PHASE_TIMEOUT = 200L;
 
     @Test
     void testInitCheckpoint() throws IOException, CheckpointException {
@@ -378,6 +384,99 @@ class SubtaskCheckpointCoordinatorTest {
         }
     }
 
+    private StreamMockEnvironment getEnvWithCheckpointSyncTimeout() {
+        return new StreamMockEnvironment(
+                new Configuration(
+                        new Configuration()
+                                .set(
+                                        
CheckpointingOptions.CHECKPOINTING_SYNC_PHASE_TIMEOUT,
+                                        
Duration.ofMillis(DEFAULT_SYNC_PHASE_TIMEOUT))),
+                new Configuration(),
+                new ExecutionConfig(),
+                1L,
+                new MockInputSplitProvider(),
+                1,
+                new TestTaskStateManager());
+    }
+
+    @Test
+    void testSyncPhaseCompletedOnTime() throws Exception {
+        // Block for half the timeout so sync phase completes on time.
+        final long operatorBlockingUntil = DEFAULT_SYNC_PHASE_TIMEOUT / 2;
+        StreamMockEnvironment env = getEnvWithCheckpointSyncTimeout();
+        OneShotLatch unblockSnapshotLatch = new OneShotLatch();
+        BlockingCheckpointOperator operator =
+                new BlockingCheckpointOperator(unblockSnapshotLatch, 
operatorBlockingUntil);
+
+        try (SubtaskCheckpointCoordinatorImpl subtaskCheckpointCoordinator =
+                (SubtaskCheckpointCoordinatorImpl)
+                        new 
MockSubtaskCheckpointCoordinatorBuilder().setEnvironment(env).build()) {
+            final OperatorChain<String, AbstractStreamOperator<String>> 
operatorChain =
+                    operatorChain(operator);
+
+            CheckpointOptions checkpointOptions =
+                    new CheckpointOptions(
+                            CheckpointType.CHECKPOINT,
+                            CheckpointStorageLocationReference.getDefault(),
+                            CheckpointOptions.AlignmentType.ALIGNED,
+                            CheckpointOptions.NO_ALIGNED_CHECKPOINT_TIME_OUT);
+            subtaskCheckpointCoordinator.checkpointState(
+                    new CheckpointMetaData(13L, System.currentTimeMillis()),
+                    checkpointOptions,
+                    new CheckpointMetricsBuilder()
+                            .setAlignmentDurationNanos(0L)
+                            .setBytesProcessedDuringAlignment(0L),
+                    operatorChain,
+                    false,
+                    () -> true);
+        }
+    }
+
+    @Test
+    void testSyncPhaseWatchDogFailsStuckTask() throws Exception {
+        final long checkpointId = 13L;
+        // block for twice the timeout so sync phase times out
+        final long operatorBlockingUntil = DEFAULT_SYNC_PHASE_TIMEOUT * 2;
+        AtomicReference<Throwable> errorRef = new AtomicReference<>();
+        StreamMockEnvironment env = getEnvWithCheckpointSyncTimeout();
+        env.setExternalExceptionHandler(errorRef::set);
+        OneShotLatch unblockSnapshotLatch = new OneShotLatch();
+        BlockingCheckpointOperator operator =
+                new BlockingCheckpointOperator(unblockSnapshotLatch, 
operatorBlockingUntil);
+
+        try (SubtaskCheckpointCoordinatorImpl subtaskCheckpointCoordinator =
+                (SubtaskCheckpointCoordinatorImpl)
+                        new 
MockSubtaskCheckpointCoordinatorBuilder().setEnvironment(env).build()) {
+            final OperatorChain<String, AbstractStreamOperator<String>> 
operatorChain =
+                    operatorChain(operator);
+
+            CheckpointOptions checkpointOptions =
+                    new CheckpointOptions(
+                            CheckpointType.CHECKPOINT,
+                            CheckpointStorageLocationReference.getDefault(),
+                            CheckpointOptions.AlignmentType.ALIGNED,
+                            CheckpointOptions.NO_ALIGNED_CHECKPOINT_TIME_OUT);
+            subtaskCheckpointCoordinator.checkpointState(
+                    new CheckpointMetaData(checkpointId, 
System.currentTimeMillis()),
+                    checkpointOptions,
+                    new CheckpointMetricsBuilder(),
+                    operatorChain,
+                    false,
+                    () -> true);
+            assertThat(errorRef.get()).isNotNull();
+            assertThat(errorRef.get().getCause())
+                    .isInstanceOf(TimeoutException.class)
+                    .hasMessageContaining(
+                            "did not complete the synchronous phase of 
checkpoint "
+                                    + checkpointId
+                                    + " within "
+                                    + DEFAULT_SYNC_PHASE_TIMEOUT
+                                    + " ms.");
+        } finally {
+            unblockSnapshotLatch.trigger();
+        }
+    }
+
     @Test
     void testBroadcastCancelCheckpointMarkerOnAbortingFromCoordinator() throws 
Exception {
         OneInputStreamTaskTestHarness<String, String> testHarness =
@@ -873,6 +972,33 @@ class SubtaskCheckpointCoordinatorTest {
         public void processWatermarkStatus(WatermarkStatus watermarkStatus) 
throws Exception {}
     }
 
+    private static final class BlockingCheckpointOperator extends 
CheckpointOperator {
+
+        private final OneShotLatch unblockSnapshotLatch;
+        private final long waitFor;
+
+        BlockingCheckpointOperator(OneShotLatch unblockSnapshotLatch, long 
waitFor) {
+            super(new OperatorSnapshotFutures());
+            this.unblockSnapshotLatch = unblockSnapshotLatch;
+            this.waitFor = waitFor;
+        }
+
+        @Override
+        public OperatorSnapshotFutures snapshotState(
+                long checkpointId,
+                long timestamp,
+                CheckpointOptions checkpointOptions,
+                CheckpointStreamFactory storageLocation)
+                throws Exception {
+            try {
+                unblockSnapshotLatch.await(waitFor, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException ignored) {
+
+            }
+            return super.snapshotState(checkpointId, timestamp, 
checkpointOptions, storageLocation);
+        }
+    }
+
     private static SubtaskCheckpointCoordinator coordinator(ChannelStateWriter 
channelStateWriter)
             throws IOException {
         return new SubtaskCheckpointCoordinatorImpl(
diff --git 
a/flink-test-utils-parent/flink-test-utils/src/main/java/org/apache/flink/streaming/util/TestStreamEnvironment.java
 
b/flink-test-utils-parent/flink-test-utils/src/main/java/org/apache/flink/streaming/util/TestStreamEnvironment.java
index 064ceeccf69..b1c0cbc9150 100644
--- 
a/flink-test-utils-parent/flink-test-utils/src/main/java/org/apache/flink/streaming/util/TestStreamEnvironment.java
+++ 
b/flink-test-utils-parent/flink-test-utils/src/main/java/org/apache/flink/streaming/util/TestStreamEnvironment.java
@@ -163,6 +163,11 @@ public class TestStreamEnvironment extends 
StreamExecutionEnvironment {
             if (!conf.contains(CheckpointingOptions.FILE_MERGING_ENABLED)) {
                 randomize(conf, CheckpointingOptions.FILE_MERGING_ENABLED, 
true);
             }
+            randomize(
+                    conf,
+                    CheckpointingOptions.CHECKPOINTING_SYNC_PHASE_TIMEOUT,
+                    CheckpointingOptions.CHECKPOINTING_TIMEOUT.defaultValue(),
+                    Duration.ofMillis(0));
         }
 
         randomize(
diff --git 
a/flink-tests/src/test/java/org/apache/flink/test/checkpointing/CheckpointSyncPhaseTimeoutITCase.java
 
b/flink-tests/src/test/java/org/apache/flink/test/checkpointing/CheckpointSyncPhaseTimeoutITCase.java
new file mode 100644
index 00000000000..5b9a27d2be8
--- /dev/null
+++ 
b/flink-tests/src/test/java/org/apache/flink/test/checkpointing/CheckpointSyncPhaseTimeoutITCase.java
@@ -0,0 +1,93 @@
+/*
+ * 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.flink.test.checkpointing;
+
+import org.apache.flink.runtime.client.JobExecutionException;
+import org.apache.flink.runtime.jobgraph.JobGraph;
+import org.apache.flink.runtime.minicluster.MiniCluster;
+import org.apache.flink.runtime.state.FunctionInitializationContext;
+import org.apache.flink.runtime.state.FunctionSnapshotContext;
+import org.apache.flink.runtime.testutils.MiniClusterResourceConfiguration;
+import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import org.apache.flink.streaming.api.functions.sink.v2.DiscardingSink;
+import org.apache.flink.streaming.api.graph.StreamingJobGraphGenerator;
+import org.apache.flink.streaming.util.RestartStrategyUtils;
+import org.apache.flink.test.junit5.InjectMiniCluster;
+import org.apache.flink.test.junit5.MiniClusterExtension;
+import org.apache.flink.test.util.InfiniteIntegerSource;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeoutException;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class CheckpointSyncPhaseTimeoutITCase extends TestLogger {
+
+    private static final long SYNC_PHASE_TIMEOUT_MILLIS = 50L;
+    private static final StreamExecutionEnvironment env = envSetup();
+
+    @RegisterExtension
+    static final MiniClusterExtension MINI_CLUSTER_EXTENSION =
+            new MiniClusterExtension(
+                    new MiniClusterResourceConfiguration.Builder()
+                            .setNumberTaskManagers(1)
+                            .setNumberSlotsPerTaskManager(1)
+                            .build());
+
+    private static StreamExecutionEnvironment envSetup() {
+        final StreamExecutionEnvironment env = 
StreamExecutionEnvironment.getExecutionEnvironment();
+        env.setParallelism(1);
+        env.enableCheckpointing(10);
+        env.getCheckpointConfig()
+                
.setCheckpointSyncPhaseTimeout(Duration.ofMillis(SYNC_PHASE_TIMEOUT_MILLIS));
+        RestartStrategyUtils.configureNoRestartStrategy(env);
+        return env;
+    }
+
+    @Test
+    void testStuckSyncPhaseFailsJob(@InjectMiniCluster MiniCluster 
miniCluster) throws Exception {
+        env.addSource(new BlockingSnapshotSource()).sinkTo(new 
DiscardingSink<>());
+        JobGraph jobGraph = 
StreamingJobGraphGenerator.createJobGraph(env.getStreamGraph());
+
+        assertThatThrownBy(() -> miniCluster.executeJobBlocking(jobGraph))
+                .isInstanceOf(JobExecutionException.class)
+                .hasRootCauseInstanceOf(TimeoutException.class)
+                .hasRootCauseMessage(
+                        "Task Source: Custom Source (1/1)#0 did not complete 
the synchronous phase of checkpoint 1 within "
+                                + SYNC_PHASE_TIMEOUT_MILLIS
+                                + " ms.");
+    }
+
+    private static class BlockingSnapshotSource extends InfiniteIntegerSource
+            implements CheckpointedFunction {
+        @Override
+        public void snapshotState(FunctionSnapshotContext context) throws 
Exception {
+            new CountDownLatch(1).await();
+        }
+
+        @Override
+        public void initializeState(FunctionInitializationContext context) {}
+    }
+}

Reply via email to