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

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


The following commit(s) were added to refs/heads/master by this push:
     new 91b28d3b2 Fix TestIndependentTaskRebalancer
91b28d3b2 is described below

commit 91b28d3b284243adefaed91ca0489f577bfe0adf
Author: Junkai Xue <[email protected]>
AuthorDate: Thu Feb 26 22:16:24 2026 -0800

    Fix TestIndependentTaskRebalancer
    
      The flaky test was caused by a race condition between:
      1. The workflow state becoming COMPLETED
      2. The task's run() method finishing and adding to _invokedClasses
      When pollForWorkflowState returns COMPLETED, there's a delay before all 
task run() methods complete and add their class names to _invokedClasses.
    
      Fix Applied
    
      1. Added polling for job context - After workflow completes, poll up to 
10 times (100ms each) to verify that tasks have been attempted via 
jobCtx.getPartitionNumAttempts(0) > 0
      2. Added additional wait - Added a 500ms sleep after workflow completion 
to allow any pending task run() methods to finish
      3. Enhanced logging - Added logging for job context attempts and invoked 
classes for debugging
---
 .../task/TestIndependentTaskRebalancer.java        | 47 ++++++++++++++++++++--
 1 file changed, 44 insertions(+), 3 deletions(-)

diff --git 
a/helix-core/src/test/java/org/apache/helix/integration/task/TestIndependentTaskRebalancer.java
 
b/helix-core/src/test/java/org/apache/helix/integration/task/TestIndependentTaskRebalancer.java
index 52bed5f78..2de2cebef 100644
--- 
a/helix-core/src/test/java/org/apache/helix/integration/task/TestIndependentTaskRebalancer.java
+++ 
b/helix-core/src/test/java/org/apache/helix/integration/task/TestIndependentTaskRebalancer.java
@@ -30,6 +30,8 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import org.apache.helix.HelixManagerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.apache.helix.InstanceType;
 import org.apache.helix.TestHelper;
 import org.apache.helix.integration.manager.ClusterControllerManager;
@@ -56,6 +58,7 @@ import org.testng.annotations.Test;
 import org.testng.collections.Sets;
 
 public class TestIndependentTaskRebalancer extends TaskTestBase {
+  private static final Logger LOG = 
LoggerFactory.getLogger(TestIndependentTaskRebalancer.class);
   private Set<String> _invokedClasses = Sets.newHashSet();
   private Map<String, Integer> _runCounts = Maps.newHashMap();
   private static final AtomicBoolean _failureCtl = new AtomicBoolean(true);
@@ -149,8 +152,13 @@ public class TestIndependentTaskRebalancer extends 
TaskTestBase {
 
   @Test
   public void testThresholdFailure() throws Exception {
+    LOG.info("Starting testThresholdFailure. Clearing invoked classes set.");
+    _invokedClasses.clear();
+    _runCounts.clear();
+
     // Create a job with two different tasks
     String jobName = TestHelper.getTestMethodName();
+    LOG.info("Creating workflow with job: " + jobName);
     Workflow.Builder workflowBuilder = new Workflow.Builder(jobName);
     List<TaskConfig> taskConfigs = Lists.newArrayListWithCapacity(2);
     Map<String, String> taskConfigMap = 
Maps.newHashMap(ImmutableMap.of("fail", "" + true));
@@ -163,15 +171,43 @@ public class TestIndependentTaskRebalancer extends 
TaskTestBase {
     JobConfig.Builder jobBuilder = new 
JobConfig.Builder().setCommand("DummyCommand")
         
.setFailureThreshold(1).addTaskConfigs(taskConfigs).setJobCommandConfigMap(jobConfigMap);
     workflowBuilder.addJob(jobName, jobBuilder);
+    LOG.info("Starting workflow: " + jobName);
     _driver.start(workflowBuilder.build());
 
     // Ensure the job completes
+    LOG.info("Polling for workflow state: " + jobName);
     _driver.pollForWorkflowState(jobName, TaskState.IN_PROGRESS);
+    LOG.info("Workflow " + jobName + " is now IN_PROGRESS");
+
     _driver.pollForWorkflowState(jobName, TaskState.COMPLETED);
+    LOG.info("Workflow " + jobName + " is now COMPLETED");
+
+    // Wait for job context to be available and verify task attempts
+    // This ensures tasks have been scheduled and executed
+    JobContext jobCtx = null;
+    for (int i = 0; i < 10; i++) {
+      jobCtx = _driver.getJobContext(jobName);
+      if (jobCtx != null && jobCtx.getPartitionNumAttempts(0) > 0) {
+        break;
+      }
+      Thread.sleep(100);
+    }
+
+    LOG.info("Job context after completion: " + (jobCtx != null ? "attempts=" 
+ jobCtx.getPartitionNumAttempts(0) : "null"));
+
+    // Additional wait to ensure all task run() methods have completed
+    // This handles the race between workflow completion and task execution 
finishing
+    Thread.sleep(500);
+
+    // Log the invoked classes for debugging
+    LOG.info("Invoked classes: " + _invokedClasses);
+    LOG.info("Run counts: " + _runCounts);
 
     // Ensure that each class was invoked
-    Assert.assertTrue(_invokedClasses.contains(TaskOne.class.getName()));
-    Assert.assertTrue(_invokedClasses.contains(TaskTwo.class.getName()));
+    Assert.assertTrue(_invokedClasses.contains(TaskOne.class.getName()),
+        "TaskOne was not invoked. Invoked classes: " + _invokedClasses);
+    Assert.assertTrue(_invokedClasses.contains(TaskTwo.class.getName()),
+        "TaskTwo was not invoked. Invoked classes: " + _invokedClasses);
   }
 
   @Test (dependsOnMethods = "testThresholdFailure")
@@ -308,15 +344,20 @@ public class TestIndependentTaskRebalancer extends 
TaskTestBase {
 
     @Override
     public synchronized TaskResult run() {
+      LOG.info("TaskOne.run() invoked on instance: " + _instanceName + ", 
shouldFail: " + _shouldFail);
       _invokedClasses.add(getClass().getName());
       _runCounts.put(_instanceName, _runCounts.get(_instanceName) + 1);
+      LOG.info("Current invoked classes: " + _invokedClasses + ", run counts: 
" + _runCounts);
 
       // Fail the task if it should fail
       if (_shouldFail) {
+        LOG.info("TaskOne on instance " + _instanceName + " returning ERROR 
status");
         return new TaskResult(Status.ERROR, null);
       }
 
-      return super.run();
+      TaskResult result = super.run();
+      LOG.info("TaskOne on instance " + _instanceName + " returning: " + 
result.getStatus());
+      return result;
     }
   }
 

Reply via email to