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 a578bada13 [common] Ensure ParallelExecution waits for reader cleanup 
(#8855)
a578bada13 is described below

commit a578bada13856dd76171036603e3ffabe448ca0d
Author: QuakeWang <[email protected]>
AuthorDate: Thu Jul 30 10:34:49 2026 +0800

    [common] Ensure ParallelExecution waits for reader cleanup (#8855)
---
 .../org/apache/paimon/utils/ParallelExecution.java | 14 ++++-
 .../paimon/crosspartition/IndexBootstrapTest.java  |  5 --
 .../apache/paimon/utils/ParallelExecutionTest.java | 67 ++++++++++++++++++++++
 3 files changed, 80 insertions(+), 6 deletions(-)

diff --git 
a/paimon-common/src/main/java/org/apache/paimon/utils/ParallelExecution.java 
b/paimon-common/src/main/java/org/apache/paimon/utils/ParallelExecution.java
index cb8b1c1f65..2b0d2a1643 100644
--- a/paimon-common/src/main/java/org/apache/paimon/utils/ParallelExecution.java
+++ b/paimon-common/src/main/java/org/apache/paimon/utils/ParallelExecution.java
@@ -173,7 +173,19 @@ public class ParallelExecution<T, E> implements Closeable {
 
     @Override
     public void close() throws IOException {
-        this.executorService.shutdownNow();
+        if (latch.getCount() == 0) {
+            this.executorService.shutdown();
+        } else {
+            this.executorService.shutdownNow();
+        }
+        try {
+            if (!this.executorService.awaitTermination(1, TimeUnit.MINUTES)) {
+                throw new IOException("Timed out while closing parallel 
execution.");
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IOException("Interrupted while closing parallel 
execution.", e);
+        }
     }
 
     private ParallelBatch<T, E> iterator(MemorySegment page, int numRecords, E 
extraMessage) {
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/crosspartition/IndexBootstrapTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/crosspartition/IndexBootstrapTest.java
index 0921ddf565..0e352b63ec 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/crosspartition/IndexBootstrapTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/crosspartition/IndexBootstrapTest.java
@@ -105,11 +105,6 @@ public class IndexBootstrapTest extends TableTestBase {
                 .containsExactlyInAnyOrder(
                         GenericRow.of(2, 1, 3), GenericRow.of(4, 2, 5), 
GenericRow.of(6, 3, 7));
         result.clear();
-
-        // In ParallelExecution, latch.countDown first, then close the reader, 
it may not be closed
-        // here, (this is good, beneficial for query speed) but 
TableTestBase.after will check leak
-        // streams. So sleep here to avoid unstable.
-        Thread.sleep(1000);
     }
 
     private Table createTable() throws Exception {
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/utils/ParallelExecutionTest.java 
b/paimon-core/src/test/java/org/apache/paimon/utils/ParallelExecutionTest.java
index 4e3feab578..f8348f5c5c 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/utils/ParallelExecutionTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/utils/ParallelExecutionTest.java
@@ -32,6 +32,13 @@ import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.function.Supplier;
 
 import static java.util.Collections.singletonList;
@@ -142,6 +149,66 @@ public class ParallelExecutionTest {
                                 + " Please increase the 'page-size' table 
option.");
     }
 
+    @Test
+    public void testCloseWaitsForReaderClose() throws Exception {
+        CountDownLatch readerCloseStarted = new CountDownLatch(1);
+        CountDownLatch readerClosed = new CountDownLatch(1);
+        Semaphore allowReaderClose = new Semaphore(0);
+        RecordReader<Integer> reader =
+                new RecordReader<Integer>() {
+                    @Nullable
+                    @Override
+                    public RecordIterator<Integer> readBatch() {
+                        return null;
+                    }
+
+                    @Override
+                    public void close() throws IOException {
+                        readerCloseStarted.countDown();
+                        try {
+                            allowReaderClose.acquire();
+                        } catch (InterruptedException e) {
+                            Thread.currentThread().interrupt();
+                            throw new IOException("Reader close was 
interrupted.", e);
+                        }
+                        readerClosed.countDown();
+                    }
+                };
+
+        ParallelExecution<Integer, Integer> execution =
+                new ParallelExecution<>(
+                        new IntSerializer(), 1024, 1, singletonList(() -> 
Pair.of(reader, 1)));
+        ExecutorService closeExecutor = Executors.newSingleThreadExecutor();
+
+        try {
+            assertThat(readerCloseStarted.await(10, 
TimeUnit.SECONDS)).isTrue();
+
+            CountDownLatch executionCloseStarted = new CountDownLatch(1);
+            Future<Void> closeFuture =
+                    closeExecutor.submit(
+                            () -> {
+                                executionCloseStarted.countDown();
+                                execution.close();
+                                return null;
+                            });
+            assertThat(executionCloseStarted.await(10, 
TimeUnit.SECONDS)).isTrue();
+            assertThatThrownBy(() -> closeFuture.get(1, TimeUnit.SECONDS))
+                    .isInstanceOf(TimeoutException.class);
+
+            allowReaderClose.release();
+            closeFuture.get(10, TimeUnit.SECONDS);
+            assertThat(readerClosed.await(10, TimeUnit.SECONDS)).isTrue();
+        } finally {
+            allowReaderClose.release();
+            try {
+                execution.close();
+                readerClosed.await(10, TimeUnit.SECONDS);
+            } finally {
+                closeExecutor.shutdownNow();
+            }
+        }
+    }
+
     private RecordReader<Integer> create(Queue<List<Integer>> queue) {
         return new RecordReader<Integer>() {
             @Nullable

Reply via email to