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

voonhous 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 08ba3f7e4e80 test(spark): de-flake TestHoodieClientMultiWriter 
early-conflict detection (#19069)
08ba3f7e4e80 is described below

commit 08ba3f7e4e80fa8c55d8fd79e04c9944c6df2d27
Author: voonhous <[email protected]>
AuthorDate: Thu Jun 25 19:50:36 2026 +0800

    test(spark): de-flake TestHoodieClientMultiWriter early-conflict detection 
(#19069)
    
    The early-conflict-detection test's commit-004 path waited
    heartBeatIntervalForCommit4 * 2 (6s) for the failed commit 003's heartbeat
    to expire, which assumed the default 
hoodie.client.heartbeat.tolerable.misses
    of 2 (expiry window = misses * interval = 2 * 3000 = 6000ms). #18904 raised
    that default to 10, growing the window to 30000ms, so commit 004 saw 003 as
    still alive and hit a false early conflict, failing assertDoesNotThrow.
    
    Because that failure threw before the un-finally'd TestingServer was closed,
    and surefire runs the shard with reuseForks=true, the leaked embedded
    ZooKeeper kept holding its admin server's fixed port 8080, so every later
    rerun failed with "Unable to instantiate ZookeeperBasedLockProvider"
    (BindException: Address already in use).
    
    Fixes:
    - Pin heartbeat tolerable misses (constant 
EARLY_CONFLICT_HEARTBEAT_TOLERABLE_MISSES)
      in buildWriteConfigForEarlyConflictDetect so the wait no longer depends 
on the
      global default, and derive the wait as interval * (misses + 1) for a 
clear margin.
    - Disable ZooKeeper's unused embedded admin server 
(zookeeper.admin.enableServer=false)
      to remove the fixed-port-8080 collision.
    - Move the TestingServer into a field closed in @AfterEach so a failing 
assertion
      cannot leak it across the reused fork.
    
    Closes #19068
---
 .../hudi/client/TestHoodieClientMultiWriter.java   | 37 ++++++++++++++++------
 1 file changed, 28 insertions(+), 9 deletions(-)

diff --git 
a/hudi-spark-datasource/hudi-spark/src/test/java/org/apache/hudi/client/TestHoodieClientMultiWriter.java
 
b/hudi-spark-datasource/hudi-spark/src/test/java/org/apache/hudi/client/TestHoodieClientMultiWriter.java
index 54476f08cdcb..8a9de11cd496 100644
--- 
a/hudi-spark-datasource/hudi-spark/src/test/java/org/apache/hudi/client/TestHoodieClientMultiWriter.java
+++ 
b/hudi-spark-datasource/hudi-spark/src/test/java/org/apache/hudi/client/TestHoodieClientMultiWriter.java
@@ -137,8 +137,21 @@ import static org.junit.jupiter.api.Assertions.fail;
 @Tag("functional")
 public class TestHoodieClientMultiWriter extends HoodieClientTestBase {
 
+  // Pin the tolerable heartbeat misses used by the early-conflict-detection 
test so its
+  // heartbeat-expiry wait does not depend on the global default
+  // (hoodie.client.heartbeat.tolerable.misses), which changed from 2 to 10 in 
#18904.
+  private static final int EARLY_CONFLICT_HEARTBEAT_TOLERABLE_MISSES = 2;
+
+  static {
+    // ZooKeeper's embedded admin server binds the fixed default port 8080 
(Curator only
+    // randomizes the client port), so a concurrent or leaked TestingServer in 
a reused
+    // fork collides with "Address already in use". The admin server is unused 
here.
+    System.setProperty("zookeeper.admin.enableServer", "false");
+  }
+
   private Properties lockProperties = null;
 
+  private TestingServer zkTestingServer = null;
 
   /**
    * super is not thread safe!!
@@ -175,6 +188,10 @@ public class TestHoodieClientMultiWriter extends 
HoodieClientTestBase {
 
   @AfterEach
   public void clean() throws IOException {
+    if (zkTestingServer != null) {
+      zkTestingServer.close();
+      zkTestingServer = null;
+    }
     cleanupResources();
   }
 
@@ -462,14 +479,14 @@ public class TestHoodieClientMultiWriter extends 
HoodieClientTestBase {
     int heartBeatIntervalForCommit4 = 3 * 1000;
 
     HoodieWriteConfig writeConfig;
-    TestingServer server = null;
     if 
(earlyConflictDetectionStrategy.equalsIgnoreCase(SimpleTransactionDirectMarkerBasedDetectionStrategy.class.getName()))
 {
       // need to setup zk related env there. Bcz 
SimpleTransactionDirectMarkerBasedDetectionStrategy is only support zk lock for 
now.
-      server = new TestingServer();
+      // zkTestingServer is closed in @AfterEach so a failing assertion cannot 
leak it (and its port-8080 admin server).
+      zkTestingServer = new TestingServer();
       Properties properties = new Properties();
       properties.setProperty(ZK_BASE_PATH_PROP_KEY, basePath);
-      properties.setProperty(ZK_CONNECT_URL_PROP_KEY, 
server.getConnectString());
-      properties.setProperty(ZK_BASE_PATH_PROP_KEY, 
server.getTempDirectory().getAbsolutePath());
+      properties.setProperty(ZK_CONNECT_URL_PROP_KEY, 
zkTestingServer.getConnectString());
+      properties.setProperty(ZK_BASE_PATH_PROP_KEY, 
zkTestingServer.getTempDirectory().getAbsolutePath());
       properties.setProperty(ZK_SESSION_TIMEOUT_MS_PROP_KEY, "10000");
       properties.setProperty(ZK_CONNECTION_TIMEOUT_MS_PROP_KEY, "10000");
       properties.setProperty(ZK_LOCK_KEY_PROP_KEY, "key");
@@ -521,8 +538,11 @@ public class TestHoodieClientMultiWriter extends 
HoodieClientTestBase {
     storage.create(heartbeatFilePath, true);
 
     // Wait for heart beat expired for failed commitTime3 "003"
-    // Otherwise commit4 still can see conflict between failed write 003.
-    Thread.sleep(heartBeatIntervalForCommit4 * 2);
+    // Otherwise commit4 still can see conflict between failed write 003. The 
early-conflict
+    // check treats 003 as alive until its heartbeat is older than
+    // (tolerable misses * heartbeat interval); tolerable misses is pinned in
+    // buildWriteConfigForEarlyConflictDetect, so wait one interval past that 
window.
+    Thread.sleep(heartBeatIntervalForCommit4 * 
(EARLY_CONFLICT_HEARTBEAT_TOLERABLE_MISSES + 1));
 
     final String nextCommitTime4 = "004";
     assertDoesNotThrow(() -> {
@@ -541,9 +561,6 @@ public class TestHoodieClientMultiWriter extends 
HoodieClientTestBase {
     assertTrue(completedInstant.contains(nextCommitTime4));
 
     FileIOUtils.deleteDirectory(new File(basePath));
-    if (server != null) {
-      server.close();
-    }
     client1.close();
     client2.close();
     client3.close();
@@ -1771,6 +1788,7 @@ public class TestHoodieClientMultiWriter extends 
HoodieClientTestBase {
     if (markerType.equalsIgnoreCase(MarkerType.DIRECT.name())) {
       return getConfigBuilder()
           .withHeartbeatIntervalInMs(60 * 1000)
+          
.withHeartbeatTolerableMisses(EARLY_CONFLICT_HEARTBEAT_TOLERABLE_MISSES)
           .withFileSystemViewConfig(FileSystemViewStorageConfig.newBuilder()
               .withStorageType(FileSystemViewStorageType.MEMORY)
               
.withSecondaryStorageType(FileSystemViewStorageType.MEMORY).build())
@@ -1791,6 +1809,7 @@ public class TestHoodieClientMultiWriter extends 
HoodieClientTestBase {
       return getConfigBuilder()
           
.withStorageConfig(HoodieStorageConfig.newBuilder().parquetMaxFileSize(20 * 
1024).build())
           .withHeartbeatIntervalInMs(60 * 1000)
+          
.withHeartbeatTolerableMisses(EARLY_CONFLICT_HEARTBEAT_TOLERABLE_MISSES)
           .withFileSystemViewConfig(FileSystemViewStorageConfig.newBuilder()
               .withStorageType(FileSystemViewStorageType.MEMORY)
               
.withSecondaryStorageType(FileSystemViewStorageType.MEMORY).build())

Reply via email to