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

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


The following commit(s) were added to refs/heads/master by this push:
     new 8e5e8502d62 Fix flaky concurrent queue and WAL tests (#18246)
8e5e8502d62 is described below

commit 8e5e8502d62f3a8310275a242eb39f42f82877ae
Author: Caideyipi <[email protected]>
AuthorDate: Mon Jul 20 16:34:58 2026 +0800

    Fix flaky concurrent queue and WAL tests (#18246)
---
 .../wal/node/WALNodeWaitForRollFileTest.java       | 141 ++++++------
 .../ConcurrentIterableLinkedQueueTest.java         | 238 ++++++++++++---------
 2 files changed, 203 insertions(+), 176 deletions(-)

diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/node/WALNodeWaitForRollFileTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/node/WALNodeWaitForRollFileTest.java
index b24a8cd29cf..1720a76cfbf 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/node/WALNodeWaitForRollFileTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/node/WALNodeWaitForRollFileTest.java
@@ -47,13 +47,12 @@ import org.junit.Test;
 
 import java.io.File;
 import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -205,7 +204,7 @@ public class WALNodeWaitForRollFileTest {
    * Verifies that waitForNextReady wakes up when a WAL file roll is triggered 
concurrently. A
    * background thread rolls the WAL file while the main thread waits on the 
iterator.
    */
-  @Test(timeout = 30000)
+  @Test
   public void testWaitForNextReadyWakesUpOnConcurrentRoll() throws Exception {
     IMemTable memTable = new PrimitiveMemTable(databasePath, dataRegionId);
     walNode.onMemTableCreated(memTable, logDirectory + File.separator + 
"test.tsfile");
@@ -223,38 +222,32 @@ public class WALNodeWaitForRollFileTest {
 
     ConsensusReqReader.ReqIterator iterator = walNode.getReqIterator(1);
 
-    AtomicBoolean found = new AtomicBoolean(false);
-    AtomicReference<Exception> error = new AtomicReference<>();
     ExecutorService executor = Executors.newSingleThreadExecutor();
-
-    // background: wait for data to become available via waitForNextReady
-    Future<?> waitFuture =
-        executor.submit(
-            () -> {
-              try {
+    CountDownLatch waiterStarted = new CountDownLatch(1);
+    try {
+      // background: wait for data to become available via waitForNextReady
+      Future<Boolean> waitFuture =
+          executor.submit(
+              () -> {
+                waiterStarted.countDown();
                 iterator.waitForNextReady(15, TimeUnit.SECONDS);
-                if (iterator.hasNext()) {
-                  found.set(true);
-                }
-              } catch (Exception e) {
-                error.set(e);
-              }
-            });
-
-    // give the waiter thread time to start blocking
-    Thread.sleep(500);
-
-    // trigger WAL file roll — this should signal rollLogWriterCondition and 
wake up the iterator
-    walNode.rollWALFile();
-    Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> 
walNode.isAllWALEntriesConsumed());
+                return iterator.hasNext();
+              });
 
-    waitFuture.get(20, TimeUnit.SECONDS);
-    executor.shutdown();
+      assertTrue(waiterStarted.await(10, TimeUnit.SECONDS));
 
-    if (error.get() != null) {
-      throw error.get();
+      // trigger WAL file roll — this should signal rollLogWriterCondition and 
wake up the iterator
+      walNode.rollWALFile();
+      Awaitility.await()
+          .atMost(10, TimeUnit.SECONDS)
+          .until(() -> walNode.isAllWALEntriesConsumed());
+
+      assertTrue(
+          "Iterator should have found data after WAL file roll",
+          waitFuture.get(20, TimeUnit.SECONDS));
+    } finally {
+      shutdownExecutor(executor);
     }
-    assertTrue("Iterator should have found data after WAL file roll", 
found.get());
   }
 
   /**
@@ -262,10 +255,12 @@ public class WALNodeWaitForRollFileTest {
    * trigger an automatic WAL file roll (file size exceeds threshold). Uses a 
small WAL file size
    * threshold to trigger the roll quickly.
    */
-  @Test(timeout = 60000)
+  @Test
   public void testWaitForNextReadyWithAutoRollOnSizeThreshold() throws 
Exception {
     // use small WAL file size to trigger auto-roll
     config.setWalFileSizeThresholdInByte(1024);
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+    CountDownLatch waiterStarted = new CountDownLatch(1);
 
     try {
       IMemTable memTable = new PrimitiveMemTable(databasePath, dataRegionId);
@@ -285,24 +280,15 @@ public class WALNodeWaitForRollFileTest {
 
       ConsensusReqReader.ReqIterator iterator = walNode.getReqIterator(1);
 
-      AtomicBoolean found = new AtomicBoolean(false);
-      AtomicReference<Exception> error = new AtomicReference<>();
-      ExecutorService executor = Executors.newSingleThreadExecutor();
-
-      Future<?> waitFuture =
+      Future<Boolean> waitFuture =
           executor.submit(
               () -> {
-                try {
-                  iterator.waitForNextReady(30, TimeUnit.SECONDS);
-                  if (iterator.hasNext()) {
-                    found.set(true);
-                  }
-                } catch (Exception e) {
-                  error.set(e);
-                }
+                waiterStarted.countDown();
+                iterator.waitForNextReady(30, TimeUnit.SECONDS);
+                return iterator.hasNext();
               });
 
-      Thread.sleep(500);
+      assertTrue(waiterStarted.await(10, TimeUnit.SECONDS));
 
       // write more data to exceed the small threshold and trigger auto-roll
       for (int i = 2; i <= 50; i++) {
@@ -314,15 +300,15 @@ public class WALNodeWaitForRollFileTest {
             Collections.singletonList(new int[] {0, node.getRowCount()}));
       }
 
-      waitFuture.get(40, TimeUnit.SECONDS);
-      executor.shutdown();
-
-      if (error.get() != null) {
-        fail("waitForNextReady threw unexpected exception: " + 
error.get().getMessage());
-      }
-      assertTrue("Iterator should have found data after auto WAL file roll", 
found.get());
+      assertTrue(
+          "Iterator should have found data after auto WAL file roll",
+          waitFuture.get(40, TimeUnit.SECONDS));
     } finally {
-      config.setWalFileSizeThresholdInByte(2 * 1024 * 1024);
+      try {
+        shutdownExecutor(executor);
+      } finally {
+        config.setWalFileSizeThresholdInByte(2 * 1024 * 1024);
+      }
     }
   }
 
@@ -332,7 +318,7 @@ public class WALNodeWaitForRollFileTest {
    * buffer → waitForRollFile(30s) times out → rollWALFile() called → data 
moves to closed file →
    * hasNext() returns true → method returns.
    */
-  @Test(timeout = 120000)
+  @Test
   public void testWaitForNextReadyAutoTriggersRollOnTimeout() throws Exception 
{
     IMemTable memTable = new PrimitiveMemTable(databasePath, dataRegionId);
     walNode.onMemTableCreated(memTable, logDirectory + File.separator + 
"test.tsfile");
@@ -352,8 +338,6 @@ public class WALNodeWaitForRollFileTest {
     ConsensusReqReader.ReqIterator iterator = walNode.getReqIterator(1);
     assertFalse("Data should not be visible before WAL file roll", 
iterator.hasNext());
 
-    AtomicBoolean found = new AtomicBoolean(false);
-    AtomicReference<Exception> error = new AtomicReference<>();
     ExecutorService executor = Executors.newSingleThreadExecutor();
 
     long startTime = System.currentTimeMillis();
@@ -362,33 +346,32 @@ public class WALNodeWaitForRollFileTest {
     // 1) wait 30s for rollLogWriterCondition (timeout)
     // 2) auto-call rollWALFile()
     // 3) data becomes readable, hasNext() returns true, method returns
-    Future<?> waitFuture =
-        executor.submit(
-            () -> {
-              try {
+    try {
+      Future<Boolean> waitFuture =
+          executor.submit(
+              () -> {
                 iterator.waitForNextReady();
-                if (iterator.hasNext()) {
-                  found.set(true);
-                }
-              } catch (Exception e) {
-                error.set(e);
-              }
-            });
-
-    waitFuture.get(90, TimeUnit.SECONDS);
-    executor.shutdown();
+                return iterator.hasNext();
+              });
 
-    long elapsed = System.currentTimeMillis() - startTime;
+      assertTrue(
+          "Iterator should have found data after auto-triggered WAL file roll",
+          waitFuture.get(90, TimeUnit.SECONDS));
 
-    if (error.get() != null) {
-      fail("waitForNextReady() threw unexpected exception: " + 
error.get().getMessage());
+      long elapsed = System.currentTimeMillis() - startTime;
+      assertTrue(
+          "Should have waited at least 30s for the timeout to trigger 
auto-roll, but only waited "
+              + elapsed
+              + "ms",
+          elapsed >= 
TimeUnit.SECONDS.toMillis(WALNode.WAIT_FOR_NEXT_WAL_ENTRY_TIMEOUT_IN_SEC - 1));
+    } finally {
+      shutdownExecutor(executor);
     }
-    assertTrue("Iterator should have found data after auto-triggered WAL file 
roll", found.get());
-    assertTrue(
-        "Should have waited at least 30s for the timeout to trigger auto-roll, 
but only waited "
-            + elapsed
-            + "ms",
-        elapsed >= 
TimeUnit.SECONDS.toMillis(WALNode.WAIT_FOR_NEXT_WAL_ENTRY_TIMEOUT_IN_SEC - 1));
+  }
+
+  private static void shutdownExecutor(final ExecutorService executor) throws 
InterruptedException {
+    executor.shutdownNow();
+    assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
   }
 
   private InsertTabletNode getInsertTabletNode(String devicePath, long[] times)
diff --git 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/ConcurrentIterableLinkedQueueTest.java
 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/ConcurrentIterableLinkedQueueTest.java
index f2b0898b418..20d27df536e 100644
--- 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/ConcurrentIterableLinkedQueueTest.java
+++ 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/ConcurrentIterableLinkedQueueTest.java
@@ -30,8 +30,10 @@ import org.junit.Test;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -79,28 +81,34 @@ public class ConcurrentIterableLinkedQueueTest {
   }
 
   @Test(timeout = 60000)
-  public void testConcurrentAddAndRemove() throws InterruptedException {
+  public void testConcurrentAddAndRemove() throws Exception {
     final int numberOfAdds = 500;
     final ExecutorService executor = Executors.newFixedThreadPool(2);
 
-    // Thread 1 adds elements to the queue
-    executor.submit(
-        () -> {
-          for (int i = 0; i < numberOfAdds; i++) {
-            queue.add(i);
-          }
-        });
-
-    // Thread 2 removes elements before a certain index
-    executor.submit(
-        () -> {
-          for (int i = 0; i < numberOfAdds; i += 10) {
-            queue.tryRemoveBefore(i);
-          }
-        });
-
-    executor.shutdown();
-    Assert.assertTrue(executor.awaitTermination(1, TimeUnit.MINUTES));
+    try {
+      // Thread 1 adds elements to the queue
+      final Future<?> addFuture =
+          executor.submit(
+              () -> {
+                for (int i = 0; i < numberOfAdds; i++) {
+                  queue.add(i);
+                }
+              });
+
+      // Thread 2 removes elements before a certain index
+      final Future<?> removeFuture =
+          executor.submit(
+              () -> {
+                for (int i = 0; i < numberOfAdds; i += 10) {
+                  queue.tryRemoveBefore(i);
+                }
+              });
+
+      addFuture.get(10, TimeUnit.SECONDS);
+      removeFuture.get(10, TimeUnit.SECONDS);
+    } finally {
+      shutdownExecutor(executor);
+    }
 
     // Validate the state of the queue
     // The actual state depends on the timing of add and remove operations
@@ -109,34 +117,48 @@ public class ConcurrentIterableLinkedQueueTest {
   }
 
   @Test(timeout = 60000)
-  public void testIterateFromEmptyQueue() {
+  public void testIterateFromEmptyQueue() throws Exception {
     final ConcurrentIterableLinkedQueue<Integer>.DynamicIterator itr = 
queue.iterateFrom(1);
-
-    final AtomicInteger value = new AtomicInteger(-1);
-    new Thread(() -> value.set(itr.next())).start();
-    queue.add(3);
-    Awaitility.await().untilAsserted(() -> Assert.assertEquals(3, 
value.get()));
+    final ExecutorService executor = Executors.newSingleThreadExecutor();
+    try {
+      final Future<Integer> valueFuture =
+          executor.submit(
+              () -> {
+                return itr.next();
+              });
+      queue.add(3);
+      Assert.assertEquals(Integer.valueOf(3), valueFuture.get(10, 
TimeUnit.SECONDS));
+    } finally {
+      shutdownExecutor(executor);
+    }
   }
 
   @Test(timeout = 60000)
-  public void testContinuousEmptyNext() throws InterruptedException {
+  public void testContinuousEmptyNext() throws Exception {
     final ConcurrentIterableLinkedQueue<Integer>.DynamicIterator itr = 
queue.iterateFrom(0);
-    final AtomicInteger consumedValue = new AtomicInteger(0);
-    new Thread(
-            () -> {
-              while (true) {
-                Integer value = itr.next(0);
-                if (value != null) {
-                  consumedValue.set(value);
+    final ExecutorService executor = Executors.newSingleThreadExecutor();
+    final CountDownLatch emptyRead = new CountDownLatch(1);
+    try {
+      final Future<Integer> consumedValueFuture =
+          executor.submit(
+              () -> {
+                while (!Thread.currentThread().isInterrupted()) {
+                  final Integer value = itr.next(0);
+                  if (value != null) {
+                    return value;
+                  }
+                  emptyRead.countDown();
                 }
-              }
-            })
-        .start();
-    Thread.sleep(6000);
-    queue.add(1);
-    Awaitility.await()
-        .atMost(100, TimeUnit.SECONDS)
-        .untilAsserted(() -> Assert.assertEquals(1, consumedValue.get()));
+                return null;
+              });
+
+      Assert.assertTrue(emptyRead.await(10, TimeUnit.SECONDS));
+      queue.add(1);
+      Assert.assertEquals(Integer.valueOf(1), consumedValueFuture.get(10, 
TimeUnit.SECONDS));
+    } finally {
+      itr.close();
+      shutdownExecutor(executor);
+    }
   }
 
   @Test(timeout = 60000)
@@ -180,7 +202,7 @@ public class ConcurrentIterableLinkedQueueTest {
   }
 
   @Test(timeout = 60000)
-  public void testIntegratedOperations() {
+  public void testIntegratedOperations() throws Exception {
     queue.add(1);
     queue.add(2);
     Assert.assertEquals(1, queue.tryRemoveBefore(1));
@@ -192,10 +214,18 @@ public class ConcurrentIterableLinkedQueueTest {
 
     final ConcurrentIterableLinkedQueue<Integer>.DynamicIterator it2 = 
queue.iterateFromLatest();
     Assert.assertEquals(2, it2.getNextIndex());
-    final AtomicInteger value = new AtomicInteger(-1);
-    new Thread(() -> value.set(it2.next())).start();
-    queue.add(3);
-    Awaitility.await().untilAsserted(() -> Assert.assertEquals(3, 
value.get()));
+    final ExecutorService executor = Executors.newSingleThreadExecutor();
+    try {
+      final Future<Integer> valueFuture =
+          executor.submit(
+              () -> {
+                return it2.next();
+              });
+      queue.add(3);
+      Assert.assertEquals(Integer.valueOf(3), valueFuture.get(10, 
TimeUnit.SECONDS));
+    } finally {
+      shutdownExecutor(executor);
+    }
 
     Assert.assertEquals(1, it.seek(Integer.MIN_VALUE));
     Assert.assertEquals(2, (int) it.next());
@@ -288,37 +318,37 @@ public class ConcurrentIterableLinkedQueueTest {
   }
 
   @Test(timeout = 60000)
-  public void testConcurrentExceptionHandling() throws InterruptedException {
+  public void testConcurrentExceptionHandling() throws Exception {
     final ExecutorService executor = Executors.newFixedThreadPool(2);
+    final CountDownLatch iteratorCreated = new CountDownLatch(1);
+    try {
+      final Future<Boolean> clearFuture =
+          executor.submit(
+              () -> {
+                queue.add(1);
+                if (!iteratorCreated.await(10, TimeUnit.SECONDS)) {
+                  return false;
+                }
+                queue.clear();
+                return true;
+              });
+
+      final Future<?> iteratorFuture =
+          executor.submit(
+              () -> {
+                final ConcurrentIterableLinkedQueue<Integer>.DynamicIterator 
itr =
+                    queue.iterateFromEarliest();
+                iteratorCreated.countDown();
+                while (itr.hasNext()) {
+                  itr.next();
+                }
+              });
 
-    executor.submit(
-        () -> {
-          queue.add(1);
-          try {
-            Thread.sleep(500); // Wait for the iterator to start
-          } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-          }
-          queue.clear();
-        });
-
-    final AtomicBoolean caughtException = new AtomicBoolean(false);
-    executor.submit(
-        () -> {
-          try {
-            final ConcurrentIterableLinkedQueue<Integer>.DynamicIterator itr =
-                queue.iterateFromEarliest();
-            while (itr.hasNext()) {
-              itr.next();
-            }
-          } catch (Exception e) {
-            caughtException.set(true);
-          }
-        });
-
-    executor.shutdown();
-    Assert.assertTrue(executor.awaitTermination(1, TimeUnit.MINUTES));
-    Assert.assertFalse(caughtException.get());
+      Assert.assertTrue(clearFuture.get(10, TimeUnit.SECONDS));
+      iteratorFuture.get(10, TimeUnit.SECONDS);
+    } finally {
+      shutdownExecutor(executor);
+    }
   }
 
   @Test(timeout = 60000)
@@ -337,18 +367,22 @@ public class ConcurrentIterableLinkedQueueTest {
   }
 
   @Test(timeout = 60000)
-  public void testMultiThreadedConsistency() throws InterruptedException {
+  public void testMultiThreadedConsistency() throws Exception {
     final int numberOfElements = 1000;
     final ExecutorService executor = Executors.newFixedThreadPool(10);
-
-    for (int i = 0; i < numberOfElements; i++) {
-      int finalI = i;
-      executor.submit(() -> queue.add(finalI));
+    final List<Future<?>> addFutures = new ArrayList<>(numberOfElements);
+    try {
+      for (int i = 0; i < numberOfElements; i++) {
+        int finalI = i;
+        addFutures.add(executor.submit(() -> queue.add(finalI)));
+      }
+      for (final Future<?> addFuture : addFutures) {
+        addFuture.get(10, TimeUnit.SECONDS);
+      }
+    } finally {
+      shutdownExecutor(executor);
     }
 
-    executor.shutdown();
-    Assert.assertTrue(executor.awaitTermination(1, TimeUnit.MINUTES));
-
     final ConcurrentIterableLinkedQueue<Integer>.DynamicIterator itr = 
queue.iterateFromEarliest();
     final HashSet<Integer> elements = new HashSet<>();
     while (itr.hasNext()) {
@@ -388,27 +422,32 @@ public class ConcurrentIterableLinkedQueueTest {
   }
 
   @Test(timeout = 60000)
-  public void testIteratorConcurrentAccess() throws InterruptedException {
+  public void testIteratorConcurrentAccess() throws Exception {
     for (int i = 0; i < 100; i++) {
       queue.add(i);
     }
 
     final ExecutorService executor = Executors.newFixedThreadPool(10);
     final AtomicInteger count = new AtomicInteger(0);
-
-    for (int i = 0; i < 10; i++) {
-      executor.submit(
-          () -> {
-            ConcurrentIterableLinkedQueue<Integer>.DynamicIterator itr = 
queue.iterateFrom(0);
-            while (itr.hasNext()) {
-              itr.next();
-              count.incrementAndGet();
-            }
-          });
+    final List<Future<?>> iteratorFutures = new ArrayList<>(10);
+    try {
+      for (int i = 0; i < 10; i++) {
+        iteratorFutures.add(
+            executor.submit(
+                () -> {
+                  ConcurrentIterableLinkedQueue<Integer>.DynamicIterator itr = 
queue.iterateFrom(0);
+                  while (itr.hasNext()) {
+                    itr.next();
+                    count.incrementAndGet();
+                  }
+                }));
+      }
+      for (final Future<?> iteratorFuture : iteratorFutures) {
+        iteratorFuture.get(10, TimeUnit.SECONDS);
+      }
+    } finally {
+      shutdownExecutor(executor);
     }
-
-    executor.shutdown();
-    Assert.assertTrue(executor.awaitTermination(1, TimeUnit.MINUTES));
     Assert.assertEquals(1000, count.get()); // 100 elements iterated by 10 
threads
   }
 
@@ -502,4 +541,9 @@ public class ConcurrentIterableLinkedQueueTest {
       Assert.assertFalse(itr.hasNext());
     }
   }
+
+  private static void shutdownExecutor(final ExecutorService executor) throws 
InterruptedException {
+    executor.shutdownNow();
+    Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
+  }
 }

Reply via email to