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

MartijnVisser 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 353c25629ca [FLINK-40916][runtime] Add option to create checkpoint 
directories on first checkpoint (#29409)
353c25629ca is described below

commit 353c25629cad73afcf74c0d098ae3173b9f35962
Author: Isaac Montenegro Jimenez <[email protected]>
AuthorDate: Tue Oct 20 20:00:34 2026 +0100

    [FLINK-40916][runtime] Add option to create checkpoint directories on first 
checkpoint (#29409)
---
 .../generated/checkpointing_configuration.html     |   6 +
 .../generated/expert_checkpointing_section.html    |   6 +
 .../flink/configuration/CheckpointingOptions.java  |  24 ++++
 .../runtime/checkpoint/CheckpointCoordinator.java  |  23 ++--
 .../tasks/CheckpointCoordinatorConfiguration.java  |  31 ++++-
 .../flink/streaming/api/graph/StreamGraph.java     |   3 +
 .../checkpoint/CheckpointCoordinatorTest.java      | 151 +++++++++++++++++++++
 .../api/graph/StreamGraphGeneratorTest.java        |  22 +++
 8 files changed, 251 insertions(+), 15 deletions(-)

diff --git a/docs/layouts/shortcodes/generated/checkpointing_configuration.html 
b/docs/layouts/shortcodes/generated/checkpointing_configuration.html
index c256e4a66e6..e5fb53d40ca 100644
--- a/docs/layouts/shortcodes/generated/checkpointing_configuration.html
+++ b/docs/layouts/shortcodes/generated/checkpointing_configuration.html
@@ -26,6 +26,12 @@
             <td>Boolean</td>
             <td>Option whether to discard a checkpoint's states in parallel 
using the ExecutorService passed into the cleaner</td>
         </tr>
+        <tr>
+            
<td><h5>execution.checkpointing.create-directories-on-job-start</h5></td>
+            <td style="word-wrap: break-word;">true</td>
+            <td>Boolean</td>
+            <td>Whether to create the directories of the checkpoint storage 
(for example the 'shared' and 'taskowned' directories) when the job starts. If 
set to false, they are created when the first checkpoint is triggered instead, 
which avoids blocking job startup on file systems where creating directories is 
a slow remote call, such as object stores.<br />WARNING: If set to false, an 
unusable checkpoint location is no longer detected when the job starts. It 
surfaces as checkpoint fai [...]
+        </tr>
         <tr>
             <td><h5>execution.checkpointing.create-subdir</h5></td>
             <td style="word-wrap: break-word;">true</td>
diff --git 
a/docs/layouts/shortcodes/generated/expert_checkpointing_section.html 
b/docs/layouts/shortcodes/generated/expert_checkpointing_section.html
index 582a61f89f3..cca09d262a4 100644
--- a/docs/layouts/shortcodes/generated/expert_checkpointing_section.html
+++ b/docs/layouts/shortcodes/generated/expert_checkpointing_section.html
@@ -8,6 +8,12 @@
         </tr>
     </thead>
     <tbody>
+        <tr>
+            
<td><h5>execution.checkpointing.create-directories-on-job-start</h5></td>
+            <td style="word-wrap: break-word;">true</td>
+            <td>Boolean</td>
+            <td>Whether to create the directories of the checkpoint storage 
(for example the 'shared' and 'taskowned' directories) when the job starts. If 
set to false, they are created when the first checkpoint is triggered instead, 
which avoids blocking job startup on file systems where creating directories is 
a slow remote call, such as object stores.<br />WARNING: If set to false, an 
unusable checkpoint location is no longer detected when the job starts. It 
surfaces as checkpoint fai [...]
+        </tr>
         <tr>
             <td><h5>execution.checkpointing.create-subdir</h5></td>
             <td style="word-wrap: break-word;">true</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 b36f9767ed4..2c9f3834177 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
@@ -231,6 +231,30 @@ public class CheckpointingOptions {
                                                     + "restoration of the 
current job when starting a new job.")
                                     .build());
 
+    /**
+     * Whether the checkpoint directories are created when the job starts, 
instead of when the first
+     * checkpoint is triggered.
+     */
+    @Documentation.Section(Documentation.Sections.EXPERT_CHECKPOINTING)
+    public static final ConfigOption<Boolean> CREATE_DIRECTORIES_ON_JOB_START =
+            
ConfigOptions.key("execution.checkpointing.create-directories-on-job-start")
+                    .booleanType()
+                    .defaultValue(true)
+                    .withDescription(
+                            Description.builder()
+                                    .text(
+                                            "Whether to create the directories 
of the checkpoint storage (for example the "
+                                                    + "'shared' and 
'taskowned' directories) when the job starts. If set to false, "
+                                                    + "they are created when 
the first checkpoint is triggered instead, which avoids "
+                                                    + "blocking job startup on 
file systems where creating directories is a slow "
+                                                    + "remote call, such as 
object stores.")
+                                    .linebreak()
+                                    .text(
+                                            "WARNING: If set to false, an 
unusable checkpoint location is no longer detected "
+                                                    + "when the job starts. It 
surfaces as checkpoint failures, and fails the job "
+                                                    + "once the tolerable 
number of checkpoint failures is exceeded.")
+                                    .build());
+
     /**
      * The minimum size of state data files. All state chunks smaller than 
that are stored inline in
      * the root checkpoint metadata file.
diff --git 
a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
 
b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
index 7750543be41..c22ee6ffe4c 100644
--- 
a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
+++ 
b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
@@ -21,6 +21,7 @@ package org.apache.flink.runtime.checkpoint;
 import org.apache.flink.annotation.VisibleForTesting;
 import org.apache.flink.api.common.JobID;
 import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.configuration.CheckpointingOptions;
 import org.apache.flink.core.execution.CheckpointType;
 import org.apache.flink.core.execution.SavepointFormatType;
 import 
org.apache.flink.runtime.checkpoint.FinishedTaskStateProvider.PartialFinishingNotSupportedByStateException;
@@ -253,7 +254,8 @@ public class CheckpointCoordinator {
     @GuardedBy("lock")
     private final Set<OperatorID> backlogOperators = new HashSet<>();
 
-    private boolean baseLocationsForCheckpointInitialized = false;
+    // Written on the IO executor.
+    private volatile boolean baseLocationsForCheckpointInitialized = false;
 
     private boolean forceFullSnapshot;
 
@@ -351,7 +353,12 @@ public class CheckpointCoordinator {
         try {
             this.checkpointStorageView = 
checkpointStorage.createCheckpointStorage(job);
 
-            if (isPeriodicCheckpointingConfigured()) {
+            if (!chkConfig.isCreateDirectoriesOnJobStart()) {
+                LOG.info(
+                        "Checkpoint directories for job {} will be created 
when the first checkpoint is triggered ({}=false).",
+                        job,
+                        
CheckpointingOptions.CREATE_DIRECTORIES_ON_JOB_START.key());
+            } else if (isPeriodicCheckpointingConfigured()) {
                 checkpointStorageView.initializeBaseLocationsForCheckpoint();
                 baseLocationsForCheckpointInitialized = true;
             }
@@ -637,9 +644,6 @@ public class CheckpointCoordinator {
             CompletableFuture<CheckpointPlan> checkpointPlanFuture =
                     checkpointPlanCalculator.calculateCheckpointPlan();
 
-            boolean initializeBaseLocations = 
!baseLocationsForCheckpointInitialized;
-            baseLocationsForCheckpointInitialized = true;
-
             CompletableFuture<Void> masterTriggerCompletionPromise = new 
CompletableFuture<>();
 
             final CompletableFuture<PendingCheckpoint> 
pendingCheckpointCompletableFuture =
@@ -679,8 +683,7 @@ public class CheckpointCoordinator {
                                                     
initializeCheckpointLocation(
                                                             
pendingCheckpoint.getCheckpointID(),
                                                             request.props,
-                                                            
request.externalSavepointLocation,
-                                                            
initializeBaseLocations);
+                                                            
request.externalSavepointLocation);
                                             return Tuple2.of(
                                                     pendingCheckpoint, 
checkpointStorageLocation);
                                         } catch (Throwable e) {
@@ -874,8 +877,7 @@ public class CheckpointCoordinator {
     private CheckpointStorageLocation initializeCheckpointLocation(
             long checkpointID,
             CheckpointProperties props,
-            @Nullable String externalSavepointLocation,
-            boolean initializeBaseLocations)
+            @Nullable String externalSavepointLocation)
             throws Exception {
         final CheckpointStorageLocation checkpointStorageLocation;
         if (props.isSavepoint()) {
@@ -883,8 +885,9 @@ public class CheckpointCoordinator {
                     checkpointStorageView.initializeLocationForSavepoint(
                             checkpointID, externalSavepointLocation);
         } else {
-            if (initializeBaseLocations) {
+            if (!baseLocationsForCheckpointInitialized) {
                 checkpointStorageView.initializeBaseLocationsForCheckpoint();
+                baseLocationsForCheckpointInitialized = true;
             }
             checkpointStorageLocation =
                     
checkpointStorageView.initializeLocationForCheckpoint(checkpointID);
diff --git 
a/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/CheckpointCoordinatorConfiguration.java
 
b/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/CheckpointCoordinatorConfiguration.java
index 1f4702213e4..bd7a4e0dc29 100644
--- 
a/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/CheckpointCoordinatorConfiguration.java
+++ 
b/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/CheckpointCoordinatorConfiguration.java
@@ -76,6 +76,8 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
 
     private final boolean pauseSourcesUntilFirstCheckpoint;
 
+    private final boolean createDirectoriesOnJobStart;
+
     /**
      * @deprecated use {@link #builder()}.
      */
@@ -105,7 +107,8 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
                 checkpointIdOfIgnoredInFlightData,
                 false,
                 false,
-                false);
+                false,
+                true);
     }
 
     private CheckpointCoordinatorConfiguration(
@@ -122,7 +125,8 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
             long checkpointIdOfIgnoredInFlightData,
             boolean enableCheckpointsAfterTasksFinish,
             boolean recoverOutputOnDownstreamTask,
-            boolean pauseSourcesUntilFirstCheckpoint) {
+            boolean pauseSourcesUntilFirstCheckpoint,
+            boolean createDirectoriesOnJobStart) {
 
         if (checkpointIntervalDuringBacklog < MINIMAL_CHECKPOINT_TIME) {
             // interval of max value means disable periodic checkpoint
@@ -164,6 +168,7 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
         this.enableCheckpointsAfterTasksFinish = 
enableCheckpointsAfterTasksFinish;
         this.recoverOutputOnDownstreamTask = recoverOutputOnDownstreamTask;
         this.pauseSourcesUntilFirstCheckpoint = 
pauseSourcesUntilFirstCheckpoint;
+        this.createDirectoriesOnJobStart = createDirectoriesOnJobStart;
     }
 
     public long getCheckpointInterval() {
@@ -222,6 +227,10 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
         return recoverOutputOnDownstreamTask;
     }
 
+    public boolean isCreateDirectoriesOnJobStart() {
+        return createDirectoriesOnJobStart;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -242,7 +251,8 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
                 && tolerableCheckpointFailureNumber == 
that.tolerableCheckpointFailureNumber
                 && checkpointIdOfIgnoredInFlightData == 
that.checkpointIdOfIgnoredInFlightData
                 && enableCheckpointsAfterTasksFinish == 
that.enableCheckpointsAfterTasksFinish
-                && recoverOutputOnDownstreamTask == 
that.recoverOutputOnDownstreamTask;
+                && recoverOutputOnDownstreamTask == 
that.recoverOutputOnDownstreamTask
+                && createDirectoriesOnJobStart == 
that.createDirectoriesOnJobStart;
     }
 
     @Override
@@ -259,7 +269,8 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
                 tolerableCheckpointFailureNumber,
                 checkpointIdOfIgnoredInFlightData,
                 enableCheckpointsAfterTasksFinish,
-                recoverOutputOnDownstreamTask);
+                recoverOutputOnDownstreamTask,
+                createDirectoriesOnJobStart);
     }
 
     @Override
@@ -289,6 +300,8 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
                 + enableCheckpointsAfterTasksFinish
                 + ", recoverOutputOnDownstreamTask="
                 + recoverOutputOnDownstreamTask
+                + ", createDirectoriesOnJobStart="
+                + createDirectoriesOnJobStart
                 + '}';
     }
 
@@ -332,6 +345,7 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
         private boolean enableCheckpointsAfterTasksFinish;
         private boolean recoverOutputOnDownstreamTask;
         private boolean pauseSourcesUntilFirstCheckpoint;
+        private boolean createDirectoriesOnJobStart = true;
 
         public CheckpointCoordinatorConfiguration build() {
             return new CheckpointCoordinatorConfiguration(
@@ -348,7 +362,8 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
                     checkpointIdOfIgnoredInFlightData,
                     enableCheckpointsAfterTasksFinish,
                     recoverOutputOnDownstreamTask,
-                    pauseSourcesUntilFirstCheckpoint);
+                    pauseSourcesUntilFirstCheckpoint,
+                    createDirectoriesOnJobStart);
         }
 
         public CheckpointCoordinatorConfigurationBuilder setCheckpointInterval(
@@ -433,5 +448,11 @@ public class CheckpointCoordinatorConfiguration implements 
Serializable {
             this.recoverOutputOnDownstreamTask = recoverOutputOnDownstreamTask;
             return this;
         }
+
+        public CheckpointCoordinatorConfigurationBuilder 
setCreateDirectoriesOnJobStart(
+                boolean createDirectoriesOnJobStart) {
+            this.createDirectoriesOnJobStart = createDirectoriesOnJobStart;
+            return this;
+        }
     }
 }
diff --git 
a/flink-runtime/src/main/java/org/apache/flink/streaming/api/graph/StreamGraph.java
 
b/flink-runtime/src/main/java/org/apache/flink/streaming/api/graph/StreamGraph.java
index 6d2e112d6c6..702fa773de6 100644
--- 
a/flink-runtime/src/main/java/org/apache/flink/streaming/api/graph/StreamGraph.java
+++ 
b/flink-runtime/src/main/java/org/apache/flink/streaming/api/graph/StreamGraph.java
@@ -401,6 +401,9 @@ public class StreamGraph implements Pipeline, ExecutionPlan 
{
                                                 
.UNALIGNED_RECOVER_OUTPUT_ON_DOWNSTREAM))
                         .setPauseSourcesUntilFirstCheckpoint(
                                 cfg.isPauseSourcesUntilFirstCheckpoint())
+                        .setCreateDirectoriesOnJobStart(
+                                jobConfiguration.get(
+                                        
CheckpointingOptions.CREATE_DIRECTORIES_ON_JOB_START))
                         .build(),
                 serializedStateBackend,
                 getJobConfiguration()
diff --git 
a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorTest.java
 
b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorTest.java
index 2b5e6653dc3..16a36b7d7bd 100644
--- 
a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorTest.java
+++ 
b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorTest.java
@@ -68,6 +68,7 @@ import org.apache.flink.runtime.state.SharedStateRegistryImpl;
 import org.apache.flink.runtime.state.StreamStateHandle;
 import org.apache.flink.runtime.state.TestingStreamStateHandle;
 import org.apache.flink.runtime.state.filesystem.FileStateHandle;
+import org.apache.flink.runtime.state.filesystem.FsCheckpointStorageAccess;
 import org.apache.flink.runtime.state.memory.ByteStreamStateHandle;
 import 
org.apache.flink.runtime.state.memory.MemoryBackendCheckpointStorageAccess;
 import 
org.apache.flink.runtime.state.memory.NonPersistentMetadataCheckpointStorageLocation;
@@ -123,6 +124,7 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
@@ -4049,6 +4051,121 @@ class CheckpointCoordinatorTest {
         assertThat(fs.exists(jobCheckpointPath)).isFalse();
     }
 
+    @Test
+    void testBaseLocationsInitializedOnJobStartNotRepeatedOnTrigger() throws 
Exception {
+        BaseLocationsTrackingCheckpointStorage storage =
+                new 
BaseLocationsTrackingCheckpointStorage(TempDirUtils.newFolder(tmpFolder), 0);
+        CheckpointCoordinator checkpointCoordinator =
+                new CheckpointCoordinatorBuilder()
+                        .setCheckpointStorage(storage)
+                        .setTimer(manuallyTriggeredScheduledExecutor)
+                        .build(EXECUTOR_RESOURCE.getExecutor());
+
+        
assertThat(checkpointCoordinator.isPeriodicCheckpointingConfigured()).isTrue();
+        assertThat(storage.getInitializeBaseLocationsCalls()).isOne();
+
+        triggerCheckpointAndRunTimer(checkpointCoordinator);
+        assertThat(storage.getInitializeBaseLocationsCalls()).isOne();
+
+        checkpointCoordinator.shutdown();
+    }
+
+    @Test
+    void 
testBaseLocationsInitializedOnFirstCheckpointWhenCreateDirectoriesOnJobStartDisabled()
+            throws Exception {
+        BaseLocationsTrackingCheckpointStorage storage =
+                new 
BaseLocationsTrackingCheckpointStorage(TempDirUtils.newFolder(tmpFolder), 0);
+        CheckpointCoordinator checkpointCoordinator =
+                new CheckpointCoordinatorBuilder()
+                        .setCheckpointCoordinatorConfiguration(
+                                CheckpointCoordinatorConfiguration.builder()
+                                        
.setMaxConcurrentCheckpoints(Integer.MAX_VALUE)
+                                        .setCreateDirectoriesOnJobStart(false)
+                                        .build())
+                        .setCheckpointStorage(storage)
+                        .setTimer(manuallyTriggeredScheduledExecutor)
+                        .build(EXECUTOR_RESOURCE.getExecutor());
+
+        
assertThat(checkpointCoordinator.isPeriodicCheckpointingConfigured()).isTrue();
+        assertThat(storage.getInitializeBaseLocationsCalls()).isZero();
+
+        triggerCheckpointAndRunTimer(checkpointCoordinator);
+        
assertThat(checkpointCoordinator.getNumberOfPendingCheckpoints()).isOne();
+        assertThat(storage.getInitializeBaseLocationsCalls()).isOne();
+
+        triggerCheckpointAndRunTimer(checkpointCoordinator);
+        
assertThat(checkpointCoordinator.getNumberOfPendingCheckpoints()).isEqualTo(2);
+        assertThat(storage.getInitializeBaseLocationsCalls()).isOne();
+
+        checkpointCoordinator.shutdown();
+    }
+
+    @Test
+    void testBaseLocationsInitializedOnFirstCheckpointAfterSavepoint() throws 
Exception {
+        BaseLocationsTrackingCheckpointStorage storage =
+                new 
BaseLocationsTrackingCheckpointStorage(TempDirUtils.newFolder(tmpFolder), 0);
+        CheckpointCoordinator checkpointCoordinator =
+                buildCoordinatorWithoutPeriodicCheckpointing(storage);
+
+        String savepointDir = 
TempDirUtils.newFolder(tmpFolder).getAbsolutePath();
+        checkpointCoordinator.triggerSavepoint(savepointDir, 
SavepointFormatType.CANONICAL);
+        manuallyTriggeredScheduledExecutor.triggerAll();
+        
assertThat(checkpointCoordinator.getNumberOfPendingCheckpoints()).isOne();
+        assertThat(storage.getInitializeBaseLocationsCalls()).isZero();
+
+        triggerCheckpointAndRunTimer(checkpointCoordinator);
+        
assertThat(checkpointCoordinator.getNumberOfPendingCheckpoints()).isEqualTo(2);
+        assertThat(storage.getInitializeBaseLocationsCalls()).isOne();
+
+        checkpointCoordinator.shutdown();
+    }
+
+    @Test
+    void 
testBaseLocationsInitializationRetriedAfterFailureAndNotRepeatedAfterSuccess()
+            throws Exception {
+        BaseLocationsTrackingCheckpointStorage storage =
+                new 
BaseLocationsTrackingCheckpointStorage(TempDirUtils.newFolder(tmpFolder), 1);
+        CheckpointCoordinator checkpointCoordinator =
+                buildCoordinatorWithoutPeriodicCheckpointing(storage);
+
+        CompletableFuture<CompletedCheckpoint> failedCheckpoint =
+                triggerCheckpointAndRunTimer(checkpointCoordinator);
+        assertThat(failedCheckpoint).isCompletedExceptionally();
+        
assertThat(checkpointCoordinator.getNumberOfPendingCheckpoints()).isZero();
+        assertThat(storage.getInitializeBaseLocationsCalls()).isOne();
+
+        triggerCheckpointAndRunTimer(checkpointCoordinator);
+        
assertThat(checkpointCoordinator.getNumberOfPendingCheckpoints()).isOne();
+        assertThat(storage.getInitializeBaseLocationsCalls()).isEqualTo(2);
+
+        triggerCheckpointAndRunTimer(checkpointCoordinator);
+        
assertThat(checkpointCoordinator.getNumberOfPendingCheckpoints()).isEqualTo(2);
+        assertThat(storage.getInitializeBaseLocationsCalls()).isEqualTo(2);
+
+        checkpointCoordinator.shutdown();
+    }
+
+    private CheckpointCoordinator buildCoordinatorWithoutPeriodicCheckpointing(
+            CheckpointStorage storage) throws Exception {
+        return new CheckpointCoordinatorBuilder()
+                .setCheckpointCoordinatorConfiguration(
+                        CheckpointCoordinatorConfiguration.builder()
+                                .setCheckpointInterval(Long.MAX_VALUE)
+                                .setMaxConcurrentCheckpoints(Integer.MAX_VALUE)
+                                .build())
+                .setCheckpointStorage(storage)
+                .setTimer(manuallyTriggeredScheduledExecutor)
+                .build(EXECUTOR_RESOURCE.getExecutor());
+    }
+
+    private CompletableFuture<CompletedCheckpoint> 
triggerCheckpointAndRunTimer(
+            CheckpointCoordinator checkpointCoordinator) {
+        CompletableFuture<CompletedCheckpoint> checkpoint =
+                checkpointCoordinator.triggerCheckpoint(false);
+        manuallyTriggeredScheduledExecutor.triggerAll();
+        return checkpoint;
+    }
+
     private CheckpointCoordinator getCheckpointCoordinator(ExecutionGraph 
graph) throws Exception {
         return new CheckpointCoordinatorBuilder()
                 .setCheckpointCoordinatorConfiguration(
@@ -4220,6 +4337,40 @@ class CheckpointCoordinatorTest {
         }
     }
 
+    /** Counts base location initializations, failing the first {@code 
failuresBeforeSuccess}. */
+    private static class BaseLocationsTrackingCheckpointStorage
+            extends FileSystemCheckpointStorage {
+        private final AtomicInteger initializeBaseLocationsCalls = new 
AtomicInteger();
+        private final int failuresBeforeSuccess;
+
+        BaseLocationsTrackingCheckpointStorage(File checkpointDir, int 
failuresBeforeSuccess) {
+            super(checkpointDir.toURI());
+            this.failuresBeforeSuccess = failuresBeforeSuccess;
+        }
+
+        int getInitializeBaseLocationsCalls() {
+            return initializeBaseLocationsCalls.get();
+        }
+
+        @Override
+        public CheckpointStorageAccess createCheckpointStorage(JobID jobId) 
throws IOException {
+            return new FsCheckpointStorageAccess(
+                    getCheckpointPath(),
+                    getSavepointPath(),
+                    jobId,
+                    getMinFileSizeThreshold(),
+                    getWriteBufferSize()) {
+                @Override
+                public void initializeBaseLocationsForCheckpoint() throws 
IOException {
+                    if (initializeBaseLocationsCalls.incrementAndGet() <= 
failuresBeforeSuccess) {
+                        throw new IOException("Failed to initialize base 
locations");
+                    }
+                    super.initializeBaseLocationsForCheckpoint();
+                }
+            };
+        }
+    }
+
     private static class StoppingCheckpointIDCounter extends 
CheckpointIDCounterWithOwner {
         @Override
         public long getAndIncrement() throws Exception {
diff --git 
a/flink-streaming-java/src/test/java/org/apache/flink/streaming/api/graph/StreamGraphGeneratorTest.java
 
b/flink-streaming-java/src/test/java/org/apache/flink/streaming/api/graph/StreamGraphGeneratorTest.java
index dbde71ef687..6384cc60f19 100644
--- 
a/flink-streaming-java/src/test/java/org/apache/flink/streaming/api/graph/StreamGraphGeneratorTest.java
+++ 
b/flink-streaming-java/src/test/java/org/apache/flink/streaming/api/graph/StreamGraphGeneratorTest.java
@@ -27,11 +27,13 @@ import org.apache.flink.api.common.state.MapStateDescriptor;
 import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
 import org.apache.flink.api.common.typeinfo.TypeInformation;
 import org.apache.flink.api.dag.Transformation;
+import org.apache.flink.configuration.CheckpointingOptions;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.configuration.StateRecoveryOptions;
 import org.apache.flink.core.memory.ManagedMemoryUseCase;
 import org.apache.flink.runtime.clusterframework.types.ResourceProfile;
 import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings;
+import org.apache.flink.runtime.jobgraph.tasks.JobCheckpointingSettings;
 import org.apache.flink.streaming.api.datastream.BroadcastStream;
 import org.apache.flink.streaming.api.datastream.CachedDataStream;
 import org.apache.flink.streaming.api.datastream.ConnectedStreams;
@@ -86,6 +88,8 @@ import org.hamcrest.FeatureMatcher;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -1255,4 +1259,22 @@ class StreamGraphGeneratorTest {
             return new HashSet<>(completedClusterDatasetIds);
         }
     }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    void 
testCreateDirectoriesOnJobStartIsForwardedToCheckpointSettings(boolean 
createOnJobStart)
+            throws Exception {
+        Configuration configuration = new Configuration();
+        
configuration.set(CheckpointingOptions.CREATE_DIRECTORIES_ON_JOB_START, 
createOnJobStart);
+        StreamExecutionEnvironment env =
+                
StreamExecutionEnvironment.getExecutionEnvironment(configuration);
+        env.enableCheckpointing(1000);
+        env.fromData(1, 2, 3).sinkTo(new DiscardingSink<>());
+
+        JobCheckpointingSettings settings =
+                env.getStreamGraph().getJobGraph().getCheckpointingSettings();
+
+        
assertThat(settings.getCheckpointCoordinatorConfiguration().isCreateDirectoriesOnJobStart())
+                .isEqualTo(createOnJobStart);
+    }
 }

Reply via email to