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

danny0405 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git


The following commit(s) were added to refs/heads/master by this push:
     new 72989b47d36f fix(flink): run mapGroupsByKey in dedicated fork join 
pool (#19819)
72989b47d36f is described below

commit 72989b47d36f841252d5943000ae2e7b2c6094f4
Author: Shuo Cheng <[email protected]>
AuthorDate: Thu Sep 3 11:58:25 2026 +0800

    fix(flink): run mapGroupsByKey in dedicated fork join pool (#19819)
    
    * fix(flink): run mapGroupsByKey in dedicated fork join pool
---
 .../client/common/HoodieFlinkEngineContext.java    | 11 ++-
 .../common/TestHoodieFlinkEngineContext.java       | 84 ++++++++++++++++++++++
 2 files changed, 93 insertions(+), 2 deletions(-)

diff --git 
a/hudi-client/hudi-flink-client/src/main/java/org/apache/hudi/client/common/HoodieFlinkEngineContext.java
 
b/hudi-client/hudi-flink-client/src/main/java/org/apache/hudi/client/common/HoodieFlinkEngineContext.java
index f88a24117047..a66fa7688486 100644
--- 
a/hudi-client/hudi-flink-client/src/main/java/org/apache/hudi/client/common/HoodieFlinkEngineContext.java
+++ 
b/hudi-client/hudi-flink-client/src/main/java/org/apache/hudi/client/common/HoodieFlinkEngineContext.java
@@ -245,11 +245,18 @@ public class HoodieFlinkEngineContext extends 
HoodieEngineContext {
                                                                                
             boolean preservesPartitioning) {
     // Group values by key and apply the function to each group in parallel
     List<Iterable<V>> groupedValues = 
data.groupByKey().values().collectAsList();
+    if (groupedValues.isEmpty()) {
+      return HoodieListData.eager(Collections.emptyList());
+    }
+
     // Process each group in parallel using parallel stream
     List<R> results = executeParallelStream(
         groupedValues.parallelStream(),
-        stream -> stream.map(values -> 
throwingMapWrapper(processFunc).apply(new 
ClosableSortingIterator<>(values.iterator()))),
-        
groupedValues.size()).flatMap(CollectionUtils::toStream).collect(Collectors.toList());
+        stream -> stream
+            .map(values -> throwingMapWrapper(processFunc).apply(new 
ClosableSortingIterator<>(values.iterator())))
+            .flatMap(CollectionUtils::toStream)
+            .collect(Collectors.toList()),
+        groupedValues.size());
     return HoodieListData.eager(results);
   }
 
diff --git 
a/hudi-client/hudi-flink-client/src/test/java/org/apache/hudi/client/common/TestHoodieFlinkEngineContext.java
 
b/hudi-client/hudi-flink-client/src/test/java/org/apache/hudi/client/common/TestHoodieFlinkEngineContext.java
index 66dd39cb3469..a506d57836b7 100644
--- 
a/hudi-client/hudi-flink-client/src/test/java/org/apache/hudi/client/common/TestHoodieFlinkEngineContext.java
+++ 
b/hudi-client/hudi-flink-client/src/test/java/org/apache/hudi/client/common/TestHoodieFlinkEngineContext.java
@@ -18,7 +18,12 @@
 
 package org.apache.hudi.client.common;
 
+import org.apache.hudi.common.data.HoodieData;
+import org.apache.hudi.common.data.HoodieListPairData;
+import org.apache.hudi.common.data.HoodiePairData;
 import org.apache.hudi.common.util.collection.ImmutablePair;
+import org.apache.hudi.common.util.collection.Pair;
+import org.apache.hudi.exception.HoodieException;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -27,8 +32,15 @@ import org.junit.jupiter.api.Test;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinTask;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Unit test against HoodieFlinkEngineContext.
@@ -89,4 +101,76 @@ public class TestHoodieFlinkEngineContext {
     Assertions.assertEquals(resultMap.get("spark"), resultMap.get("flink"));
   }
 
+  @Test
+  public void testMapGroupsByKeyUsesDedicatedForkJoinPool() {
+    HoodiePairData<Integer, Integer> input = 
HoodieListPairData.eager(Arrays.asList(
+        Pair.of(1, 3), Pair.of(1, 1), Pair.of(2, 4), Pair.of(2, 2), Pair.of(3, 
5)));
+    Set<ForkJoinPool> executingPools = ConcurrentHashMap.newKeySet();
+    AtomicBoolean executedOutsideForkJoinPool = new AtomicBoolean(false);
+
+    HoodieData<Integer> result = context.mapGroupsByKey(input, values -> {
+      recordExecutingPool(executingPools, executedOutsideForkJoinPool);
+      return new Iterator<Integer>() {
+        @Override
+        public boolean hasNext() {
+          recordExecutingPool(executingPools, executedOutsideForkJoinPool);
+          return values.hasNext();
+        }
+
+        @Override
+        public Integer next() {
+          recordExecutingPool(executingPools, executedOutsideForkJoinPool);
+          return values.next();
+        }
+      };
+    }, Arrays.asList(1, 2, 3), false);
+
+    Assertions.assertFalse(executedOutsideForkJoinPool.get());
+    Assertions.assertEquals(1, executingPools.size());
+    Assertions.assertNotSame(ForkJoinPool.commonPool(), 
executingPools.iterator().next());
+    Assertions.assertEquals(3, 
executingPools.iterator().next().getParallelism());
+    List<Integer> actual = result.collectAsList();
+    Collections.sort(actual);
+    Assertions.assertEquals(Arrays.asList(1, 2, 3, 4, 5), actual);
+  }
+
+  @Test
+  public void testMapGroupsByKeyWithEmptyInput() {
+    HoodiePairData<Integer, Integer> input = 
HoodieListPairData.eager(Collections.emptyList());
+
+    HoodieData<Integer> result = context.mapGroupsByKey(
+        input, values -> values, Collections.emptyList(), false);
+
+    Assertions.assertTrue(result.collectAsList().isEmpty());
+  }
+
+  @Test
+  public void testMapGroupsByKeyWrapsProcessFunctionFailure() {
+    HoodiePairData<Integer, Integer> input = HoodieListPairData.eager(
+        Collections.singletonList(Pair.of(1, 1)));
+    IllegalStateException originalFailure = new IllegalStateException("process 
function failure");
+
+    HoodieException failure = Assertions.assertThrows(HoodieException.class, 
() ->
+        context.mapGroupsByKey(input, values -> {
+          throw originalFailure;
+        }, Collections.singletonList(1), false));
+
+    Assertions.assertEquals("Failed to execute parallel stream with dedicated 
ForkJoinPool.", failure.getMessage());
+    Throwable rootCause = failure;
+    while (rootCause.getCause() != null) {
+      rootCause = rootCause.getCause();
+    }
+    Assertions.assertSame(originalFailure, rootCause);
+  }
+
+  private static void recordExecutingPool(Set<ForkJoinPool> executingPools,
+                                          AtomicBoolean 
executedOutsideForkJoinPool) {
+    ForkJoinPool executingPool = ForkJoinTask.getPool();
+    if (executingPool == null) {
+      executedOutsideForkJoinPool.set(true);
+    } else {
+      executingPools.add(executingPool);
+    }
+  }
+
 }

Reply via email to