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

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


The following commit(s) were added to refs/heads/master by this push:
     new 4efaa151d78 Fix two flaky unit tests exposed under parallel-fork CI 
load (#19272)
4efaa151d78 is described below

commit 4efaa151d78e97247400ad14c1b5ee2e4741be8c
Author: Xiang Fu <[email protected]>
AuthorDate: Mon Aug 17 12:17:46 2026 -0700

    Fix two flaky unit tests exposed under parallel-fork CI load (#19272)
    
    SegmentDeletionManagerTest.testRemoveDeletedSegments:
    Aged files are deleted asynchronously on SegmentDeletionManager's executor,
    while an empty deleted-segments directory is only removed synchronously on a
    subsequent run. The barrier before the second removeAgedDeletedSegments() 
call
    only waited for dummyDir2.exists(), which is trivially true from directory
    creation. Under load the second run could race ahead of the first run's 
async
    file deletions, still observe files in dummyDir2, skip the empty-directory
    removal, and never delete the directory -- surfacing as a 120s "dummyDir2 
still
    exists" timeout. Strengthen the barrier to wait until dummyDir2 exists AND 
is
    empty before triggering the next run.
    
    DimensionTableDataManagerTest:
    DimensionTableDataManager is a process-wide singleton keyed by table name
    (static INSTANCES map). Every test method loads the same 
dimBaseballTeams_OFFLINE
    table with no per-method teardown, so the singleton -- with its 
property-store
    mock, loaded segments, and reload executor -- leaked across methods. A stale
    async reload from a prior method could read a _propertyStore that a 
concurrent
    init() had swapped out, intermittently failing testReloadTable with "Failed 
to
    find schema for table: dimBaseballTeams_OFFLINE". Add an @AfterMethod that 
shuts
    the singleton down (removing it from INSTANCES) so each method starts from a
    clean, freshly-initialized instance.
    
    Both are root-cause synchronization/isolation fixes, not sleep/retry masks.
---
 .../helix/core/util/SegmentDeletionManagerTest.java    | 15 +++++++++++----
 .../manager/offline/DimensionTableDataManagerTest.java | 18 ++++++++++++++++++
 2 files changed, 29 insertions(+), 4 deletions(-)

diff --git 
a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java
 
b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java
index bf36f7a8660..10d8c6beb5e 100644
--- 
a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java
+++ 
b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java
@@ -300,15 +300,22 @@ public class SegmentDeletionManagerTest {
     TestUtils.waitForCondition((aVoid) -> dummyDir1.list().length == 1, 2000, 
120000,
         "Unable to delete desired segments from dummyDir1");
 
-    // Check that empty directory has not been removed in the first run
-    TestUtils.waitForCondition((aVoid) -> dummyDir2.exists(), 2000, 120000,
-        "dummyDir2 does not exist");
+    // dummyDir2's files are all aged, so the first run deletes them 
(asynchronously, on the deletion
+    // manager's executor) but leaves the now-empty directory in place. Empty 
directories are only
+    // removed on a subsequent run. Wait until the directory is present AND 
empty before triggering
+    // that next run: this barrier both asserts the "directory survives the 
first run" behavior and,
+    // critically, guarantees the async file deletions have completed. 
Previously this only waited for
+    // dummyDir2.exists() (trivially true from the moment it is created), so 
the second run below could
+    // race ahead of the async deletions, still see files in dummyDir2, skip 
the empty-directory
+    // removal, and never delete the directory -- surfacing as a 120s 
"dummyDir2 still exists" timeout.
+    TestUtils.waitForCondition((aVoid) -> dummyDir2.exists() && 
dummyDir2.list().length == 0, 2000, 120000,
+        "dummyDir2 was not emptied by the first deletion run");
 
     // Check that deleted file without retention suffix is honoring 
cluster-wide retention period of 7 days.
     TestUtils.waitForCondition((aVoid) -> dummyDir3.list().length == 1, 2000, 
120000,
         "Unable to delete desired segments from dummyDir3");
 
-    // Try to remove empty directory in the next run
+    // Try to remove the now-empty directory in the next run
     deletionManager.removeAgedDeletedSegments(leadControllerManager);
     TestUtils.waitForCondition((aVoid) -> !dummyDir2.exists(), 2000, 120000,
         "dummyDir2 still exists");
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManagerTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManagerTest.java
index 2bae01b1e41..57bbc31018f 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManagerTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManagerTest.java
@@ -65,6 +65,7 @@ import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
 import org.apache.pinot.spi.utils.builder.TableNameBuilder;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
@@ -124,6 +125,23 @@ public class DimensionTableDataManagerTest {
     _segmentZKMetadata.setCrc(Long.parseLong(segmentMetadata.getCrc()));
   }
 
+  @AfterMethod(alwaysRun = true)
+  public void tearDownMethod() {
+    // DimensionTableDataManager is a process-wide singleton keyed by table 
name (see the static
+    // INSTANCES map in DimensionTableDataManager). Every test method loads 
the same
+    // dimBaseballTeams_OFFLINE table, so without an explicit teardown the 
singleton (and its
+    // property-store mock, loaded segments, and reload executor) leaks from 
one method into the
+    // next. A stale async reload from a prior method could then read a 
_propertyStore that a
+    // concurrent init() had swapped out, surfacing as an intermittent
+    // "Failed to find schema for table: dimBaseballTeams_OFFLINE". Shutting 
the singleton down
+    // removes it from INSTANCES so each method starts from a clean, 
freshly-initialized instance.
+    DimensionTableDataManager tableDataManager =
+        DimensionTableDataManager.getInstanceByTableName(OFFLINE_TABLE_NAME);
+    if (tableDataManager != null) {
+      tableDataManager.shutDown();
+    }
+  }
+
   @AfterClass
   public void tearDown() {
     FileUtils.deleteQuietly(TEMP_DIR);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to