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 31fb173d9cb [FLINK-38536][tests] Fix data race in FinalizeOnMasterTest 
and ExecutionGraphFinishTest (#28350)
31fb173d9cb is described below

commit 31fb173d9cb0234c67214153fea11841fd36a38a
Author: Martijn Visser <[email protected]>
AuthorDate: Mon Jun 29 22:07:04 2026 +0200

    [FLINK-38536][tests] Fix data race in FinalizeOnMasterTest and 
ExecutionGraphFinishTest (#28350)
    
    * [FLINK-38536][tests] Fix data race in FinalizeOnMasterTest
    
    forMainThread() runs the async deployment callbacks inline on the I/O 
thread, racing
    with the test thread that schedules the execution graph. Run all execution 
graph
    interactions on a dedicated single-threaded main-thread executor instead, 
and drop
    the temporary debug logging from PR #27168.
    
    Generated-by: Claude Opus 4.8 (1M context)
    
    * [FLINK-38536][tests] Fix same data race in ExecutionGraphFinishTest
    
    testJobFinishes() shares the forMainThread() wiring and the same 
deployment-callback
    race fixed in FinalizeOnMasterTest. Apply the same fix.
    
    Generated-by: Claude Opus 4.8 (1M context)
---
 .../executiongraph/ExecutionGraphFinishTest.java   | 103 ++++++++++++++++-----
 .../executiongraph/FinalizeOnMasterTest.java       |  82 +++++++++++-----
 2 files changed, 138 insertions(+), 47 deletions(-)

diff --git 
a/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/ExecutionGraphFinishTest.java
 
b/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/ExecutionGraphFinishTest.java
index a096ebabbc7..148ee0d101e 100644
--- 
a/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/ExecutionGraphFinishTest.java
+++ 
b/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/ExecutionGraphFinishTest.java
@@ -19,6 +19,7 @@
 package org.apache.flink.runtime.executiongraph;
 
 import org.apache.flink.api.common.JobStatus;
+import org.apache.flink.runtime.concurrent.ComponentMainThreadExecutor;
 import 
org.apache.flink.runtime.concurrent.ComponentMainThreadExecutorServiceAdapter;
 import org.apache.flink.runtime.jobgraph.JobGraph;
 import org.apache.flink.runtime.jobgraph.JobGraphTestUtils;
@@ -34,58 +35,110 @@ import org.junit.jupiter.api.extension.RegisterExtension;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Supplier;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 /** Tests the finish behaviour of the {@link ExecutionGraph}. */
 class ExecutionGraphFinishTest {
 
+    /**
+     * Dedicated single-threaded main-thread executor; using forMainThread() 
here would run the
+     * deployment callbacks on the I/O thread and race the test (FLINK-38536).
+     */
+    @RegisterExtension
+    static final TestExecutorExtension<ScheduledExecutorService> 
JM_MAIN_THREAD_EXECUTOR_RESOURCE =
+            TestingUtils.jmMainThreadExecutorExtension();
+
     @RegisterExtension
     static final TestExecutorExtension<ScheduledExecutorService> 
EXECUTOR_RESOURCE =
             TestingUtils.defaultExecutorExtension();
 
     @Test
     void testJobFinishes() throws Exception {
-
         JobGraph jobGraph =
                 JobGraphTestUtils.streamingJobGraph(
                         ExecutionGraphTestUtils.createJobVertex("Task1", 2, 
NoOpInvokable.class),
                         ExecutionGraphTestUtils.createJobVertex("Task2", 2, 
NoOpInvokable.class));
 
+        final ComponentMainThreadExecutor mainThreadExecutor =
+                
ComponentMainThreadExecutorServiceAdapter.forSingleThreadExecutor(
+                        JM_MAIN_THREAD_EXECUTOR_RESOURCE.getExecutor());
+
         SchedulerBase scheduler =
                 new DefaultSchedulerBuilder(
-                                jobGraph,
-                                
ComponentMainThreadExecutorServiceAdapter.forMainThread(),
-                                EXECUTOR_RESOURCE.getExecutor())
+                                jobGraph, mainThreadExecutor, 
EXECUTOR_RESOURCE.getExecutor())
                         .build();
 
         ExecutionGraph eg = scheduler.getExecutionGraph();
 
-        scheduler.startScheduling();
-        ExecutionGraphTestUtils.switchAllVerticesToRunning(eg);
-
-        Iterator<ExecutionJobVertex> jobVertices = 
eg.getVerticesTopologically().iterator();
-
-        ExecutionJobVertex sender = jobVertices.next();
-        ExecutionJobVertex receiver = jobVertices.next();
-
-        List<ExecutionVertex> senderVertices = 
Arrays.asList(sender.getTaskVertices());
-        List<ExecutionVertex> receiverVertices = 
Arrays.asList(receiver.getTaskVertices());
+        runInMainThread(mainThreadExecutor, scheduler::startScheduling);
+        runInMainThread(
+                mainThreadExecutor, () -> 
ExecutionGraphTestUtils.switchAllVerticesToRunning(eg));
+
+        final ExecutionJobVertex[] orderedVertices =
+                supplyInMainThread(
+                        mainThreadExecutor,
+                        () -> {
+                            Iterator<ExecutionJobVertex> jobVertices =
+                                    eg.getVerticesTopologically().iterator();
+                            return new ExecutionJobVertex[] {
+                                jobVertices.next(), jobVertices.next()
+                            };
+                        });
+        ExecutionJobVertex sender = orderedVertices[0];
+        ExecutionJobVertex receiver = orderedVertices[1];
+
+        List<ExecutionVertex> senderVertices =
+                supplyInMainThread(
+                        mainThreadExecutor, () -> 
Arrays.asList(sender.getTaskVertices()));
+        List<ExecutionVertex> receiverVertices =
+                supplyInMainThread(
+                        mainThreadExecutor, () -> 
Arrays.asList(receiver.getTaskVertices()));
 
         // test getNumExecutionVertexFinished
-        senderVertices.get(0).getCurrentExecutionAttempt().markFinished();
-        assertThat(sender.getNumExecutionVertexFinished()).isOne();
-        assertThat(eg.getState()).isEqualTo(JobStatus.RUNNING);
-
-        senderVertices.get(1).getCurrentExecutionAttempt().markFinished();
-        assertThat(sender.getNumExecutionVertexFinished()).isEqualTo(2);
-        assertThat(eg.getState()).isEqualTo(JobStatus.RUNNING);
+        runInMainThread(
+                mainThreadExecutor,
+                () -> 
senderVertices.get(0).getCurrentExecutionAttempt().markFinished());
+        assertThat(supplyInMainThread(mainThreadExecutor, 
sender::getNumExecutionVertexFinished))
+                .isOne();
+        assertThat(supplyInMainThread(mainThreadExecutor, eg::getState))
+                .isEqualTo(JobStatus.RUNNING);
+
+        runInMainThread(
+                mainThreadExecutor,
+                () -> 
senderVertices.get(1).getCurrentExecutionAttempt().markFinished());
+        assertThat(supplyInMainThread(mainThreadExecutor, 
sender::getNumExecutionVertexFinished))
+                .isEqualTo(2);
+        assertThat(supplyInMainThread(mainThreadExecutor, eg::getState))
+                .isEqualTo(JobStatus.RUNNING);
 
         // test job finishes
-        receiverVertices.get(0).getCurrentExecutionAttempt().markFinished();
-        receiverVertices.get(1).getCurrentExecutionAttempt().markFinished();
-        assertThat(eg.getNumFinishedVertices()).isEqualTo(4);
-        assertThat(eg.getState()).isEqualTo(JobStatus.FINISHED);
+        runInMainThread(
+                mainThreadExecutor,
+                () -> {
+                    
receiverVertices.get(0).getCurrentExecutionAttempt().markFinished();
+                    
receiverVertices.get(1).getCurrentExecutionAttempt().markFinished();
+                });
+        assertThat(eg.waitUntilTerminal()).isEqualTo(JobStatus.FINISHED);
+        assertThat(supplyInMainThread(mainThreadExecutor, 
eg::getNumFinishedVertices)).isEqualTo(4);
+        assertThat(supplyInMainThread(mainThreadExecutor, eg::getState))
+                .isEqualTo(JobStatus.FINISHED);
+    }
+
+    /** Runs the action on the JobManager main thread and blocks until it 
completes. */
+    private static void runInMainThread(
+            final ComponentMainThreadExecutor mainThreadExecutor, final 
Runnable action)
+            throws Exception {
+        CompletableFuture.runAsync(action, mainThreadExecutor).get();
+    }
+
+    /** Reads the value on the JobManager main thread and blocks until it is 
available. */
+    private static <T> T supplyInMainThread(
+            final ComponentMainThreadExecutor mainThreadExecutor, final 
Supplier<T> supplier)
+            throws Exception {
+        return CompletableFuture.supplyAsync(supplier, 
mainThreadExecutor).get();
     }
 }
diff --git 
a/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/FinalizeOnMasterTest.java
 
b/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/FinalizeOnMasterTest.java
index 9a2066fdfd0..a2b792ee239 100644
--- 
a/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/FinalizeOnMasterTest.java
+++ 
b/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/FinalizeOnMasterTest.java
@@ -19,6 +19,7 @@
 package org.apache.flink.runtime.executiongraph;
 
 import org.apache.flink.api.common.JobStatus;
+import org.apache.flink.runtime.concurrent.ComponentMainThreadExecutor;
 import 
org.apache.flink.runtime.concurrent.ComponentMainThreadExecutorServiceAdapter;
 import org.apache.flink.runtime.jobgraph.JobGraphTestUtils;
 import org.apache.flink.runtime.jobgraph.JobVertex;
@@ -30,10 +31,10 @@ import 
org.apache.flink.testutils.executor.TestExecutorExtension;
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Supplier;
 
 import static 
org.apache.flink.runtime.scheduler.SchedulerTestingUtils.createScheduler;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -47,7 +48,14 @@ import static org.mockito.Mockito.verify;
  * only when the execution graph reaches the successful final state.
  */
 class FinalizeOnMasterTest {
-    private static final Logger LOG = 
LoggerFactory.getLogger(FinalizeOnMasterTest.class);
+
+    /**
+     * Dedicated single-threaded main-thread executor; using forMainThread() 
here would run the
+     * deployment callbacks on the I/O thread and race the test (FLINK-38536).
+     */
+    @RegisterExtension
+    static final TestExecutorExtension<ScheduledExecutorService> 
JM_MAIN_THREAD_EXECUTOR_RESOURCE =
+            TestingUtils.jmMainThreadExecutorExtension();
 
     @RegisterExtension
     static final TestExecutorExtension<ScheduledExecutorService> 
EXECUTOR_RESOURCE =
@@ -63,31 +71,35 @@ class FinalizeOnMasterTest {
         vertex2.setInvokableClass(NoOpInvokable.class);
         vertex2.setParallelism(2);
 
+        final ComponentMainThreadExecutor mainThreadExecutor =
+                
ComponentMainThreadExecutorServiceAdapter.forSingleThreadExecutor(
+                        JM_MAIN_THREAD_EXECUTOR_RESOURCE.getExecutor());
+
         final SchedulerBase scheduler =
                 createScheduler(
                         JobGraphTestUtils.streamingJobGraph(vertex1, vertex2),
-                        
ComponentMainThreadExecutorServiceAdapter.forMainThread(),
+                        mainThreadExecutor,
                         EXECUTOR_RESOURCE.getExecutor());
-        scheduler.startScheduling();
 
         final ExecutionGraph eg = scheduler.getExecutionGraph();
-        if (!eg.getState().equals(JobStatus.RUNNING)) {
-            ErrorInfo ei = eg.getFailureInfo();
-            LOG.info("Unexpected state found: Exception as string " + 
ei.getExceptionAsString());
-            LOG.info("Unexpected state found: ErrorInfo as string " + ei);
-        }
-        assertThat(eg.getState()).isEqualTo(JobStatus.RUNNING);
 
-        ExecutionGraphTestUtils.switchAllVerticesToRunning(eg);
+        runInMainThread(mainThreadExecutor, scheduler::startScheduling);
+        assertThat(supplyInMainThread(mainThreadExecutor, eg::getState))
+                .isEqualTo(JobStatus.RUNNING);
+
+        runInMainThread(
+                mainThreadExecutor, () -> 
ExecutionGraphTestUtils.switchAllVerticesToRunning(eg));
 
         // move all vertices to finished state
-        ExecutionGraphTestUtils.finishAllVertices(eg);
+        runInMainThread(mainThreadExecutor, () -> 
ExecutionGraphTestUtils.finishAllVertices(eg));
         assertThat(eg.waitUntilTerminal()).isEqualTo(JobStatus.FINISHED);
 
+        // waitUntilTerminal acts as the barrier, so the assertions below are 
safe off the main
+        // thread.
         verify(vertex1, 
times(1)).finalizeOnMaster(any(FinalizeOnMasterContext.class));
         verify(vertex2, 
times(1)).finalizeOnMaster(any(FinalizeOnMasterContext.class));
 
-        assertThat(eg.getRegisteredExecutions()).isEmpty();
+        assertThat(supplyInMainThread(mainThreadExecutor, 
eg::getRegisteredExecutions)).isEmpty();
     }
 
     @Test
@@ -96,28 +108,54 @@ class FinalizeOnMasterTest {
         vertex.setInvokableClass(NoOpInvokable.class);
         vertex.setParallelism(1);
 
+        final ComponentMainThreadExecutor mainThreadExecutor =
+                
ComponentMainThreadExecutorServiceAdapter.forSingleThreadExecutor(
+                        JM_MAIN_THREAD_EXECUTOR_RESOURCE.getExecutor());
+
         final SchedulerBase scheduler =
                 createScheduler(
                         JobGraphTestUtils.streamingJobGraph(vertex),
-                        
ComponentMainThreadExecutorServiceAdapter.forMainThread(),
+                        mainThreadExecutor,
                         EXECUTOR_RESOURCE.getExecutor());
-        scheduler.startScheduling();
 
         final ExecutionGraph eg = scheduler.getExecutionGraph();
 
-        assertThat(eg.getState()).isEqualTo(JobStatus.RUNNING);
+        runInMainThread(mainThreadExecutor, scheduler::startScheduling);
+        assertThat(supplyInMainThread(mainThreadExecutor, eg::getState))
+                .isEqualTo(JobStatus.RUNNING);
 
-        ExecutionGraphTestUtils.switchAllVerticesToRunning(eg);
+        runInMainThread(
+                mainThreadExecutor, () -> 
ExecutionGraphTestUtils.switchAllVerticesToRunning(eg));
 
         // fail the execution
-        final Execution exec =
-                
eg.getJobVertex(vertex.getID()).getTaskVertices()[0].getCurrentExecutionAttempt();
-        exec.fail(new Exception("test"));
+        runInMainThread(
+                mainThreadExecutor,
+                () -> {
+                    final Execution exec =
+                            eg.getJobVertex(vertex.getID())
+                                    .getTaskVertices()[0]
+                                    .getCurrentExecutionAttempt();
+                    exec.fail(new Exception("test"));
+                });
 
         assertThat(eg.waitUntilTerminal()).isEqualTo(JobStatus.FAILED);
 
         verify(vertex, 
times(0)).finalizeOnMaster(any(FinalizeOnMasterContext.class));
 
-        assertThat(eg.getRegisteredExecutions()).isEmpty();
+        assertThat(supplyInMainThread(mainThreadExecutor, 
eg::getRegisteredExecutions)).isEmpty();
+    }
+
+    /** Runs the action on the JobManager main thread and blocks until it 
completes. */
+    private static void runInMainThread(
+            final ComponentMainThreadExecutor mainThreadExecutor, final 
Runnable action)
+            throws Exception {
+        CompletableFuture.runAsync(action, mainThreadExecutor).get();
+    }
+
+    /** Reads the value on the JobManager main thread and blocks until it is 
available. */
+    private static <T> T supplyInMainThread(
+            final ComponentMainThreadExecutor mainThreadExecutor, final 
Supplier<T> supplier)
+            throws Exception {
+        return CompletableFuture.supplyAsync(supplier, 
mainThreadExecutor).get();
     }
 }

Reply via email to