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

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

commit 3af496a418cf9d1e4431b04e269c1ba97cc90688
Author: Tian Jiang <[email protected]>
AuthorDate: Thu Jul 16 14:02:21 2026 +0800

    fix TierManager may access an empty list during re-initialization
---
 .../db/storageengine/rescon/disk/TierManager.java  |  24 ++--
 .../storageengine/rescon/disk/TierManagerTest.java | 139 +++++++++++++++++++++
 2 files changed, 156 insertions(+), 7 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
index 3e47ce442f7..b23a3e081e5 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
@@ -62,15 +62,15 @@ public class TierManager {
   /**
    * seq folder manager of each storage tier, managing both data directories 
and multi-dir strategy
    */
-  private final List<FolderManager> seqTiers = new ArrayList<>();
+  private volatile List<FolderManager> seqTiers = new ArrayList<>();
 
   /**
    * unSeq folder manager of each storage tier, managing both data directories 
and multi-dir
    * strategy
    */
-  private final List<FolderManager> unSeqTiers = new ArrayList<>();
+  private volatile List<FolderManager> unSeqTiers = new ArrayList<>();
 
-  private final List<FolderManager> objectTiers = new ArrayList<>();
+  private volatile List<FolderManager> objectTiers = new ArrayList<>();
 
   /** seq file folder's rawFsPath path -> tier level */
   private final Map<String, Integer> seqDir2TierLevel = new HashMap<>();
@@ -95,6 +95,13 @@ public class TierManager {
   }
 
   public synchronized void initFolders() {
+    initFolders(seqTiers, unSeqTiers, objectTiers);
+  }
+
+  private void initFolders(
+      List<FolderManager> seqTiers,
+      List<FolderManager> unSeqTiers,
+      List<FolderManager> objectTiers) {
     directoryStrategyType =
         
DirectoryStrategyType.fromClassName(config.getMultiDirStrategyClassName());
 
@@ -204,13 +211,16 @@ public class TierManager {
 
   public synchronized void resetFolders() {
     long startTime = System.currentTimeMillis();
-    seqTiers.clear();
-    unSeqTiers.clear();
-    objectTiers.clear();
+    List<FolderManager> newSeqTiers = new ArrayList<>();
+    List<FolderManager> newUnSeqTiers = new ArrayList<>();
+    List<FolderManager> newObjectTiers = new ArrayList<>();
     seqDir2TierLevel.clear();
     unSeqDir2TierLevel.clear();
 
-    initFolders();
+    initFolders(newSeqTiers, newUnSeqTiers, newObjectTiers);
+    seqTiers = newSeqTiers;
+    unSeqTiers = newUnSeqTiers;
+    objectTiers = newObjectTiers;
     long endTime = System.currentTimeMillis();
     logger.info(StorageEngineMessages.FOLDERS_RESET_SUCCESSFULLY, (endTime - 
startTime));
   }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManagerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManagerTest.java
new file mode 100644
index 00000000000..f11f37caa78
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManagerTest.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.storageengine.rescon.disk;
+
+import org.apache.iotdb.db.conf.IoTDBConfig;
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+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;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class TierManagerTest {
+
+  private static final int DATA_DIR_NUM = 16;
+  private static final int RESET_TIMES = 20;
+
+  @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  private final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig();
+  private String[][] originalTierDataDirs;
+  private TierManager tierManager;
+
+  @Before
+  public void setUp() {
+    originalTierDataDirs = config.getTierDataDirs();
+    String[] dataDirs = new String[DATA_DIR_NUM];
+    for (int i = 0; i < DATA_DIR_NUM; i++) {
+      dataDirs[i] = new File(temporaryFolder.getRoot(), "data" + 
i).getAbsolutePath();
+    }
+    config.setTierDataDirs(new String[][] {dataDirs});
+    tierManager = TierManager.getInstance();
+    tierManager.resetFolders();
+  }
+
+  @After
+  public void tearDown() {
+    config.setTierDataDirs(originalTierDataDirs);
+    tierManager.resetFolders();
+  }
+
+  @Test
+  public void testConcurrentResetFoldersAndGetNextFolder() throws Exception {
+    List<Callable<?>> folderAccessors =
+        Arrays.asList(
+            () -> tierManager.getNextFolderForTsFile(0, true),
+            () -> tierManager.getNextFolderForTsFile(0, false),
+            tierManager::getNextFolderForObjectFile,
+            tierManager::getNextFolderForCopyToTargetFile,
+            () -> tierManager.getFolderManager(0, true),
+            () -> tierManager.getFolderManager(0, false),
+            () -> {
+              int tiersNum = tierManager.getTiersNum();
+              assertTrue(tiersNum > 0);
+              return tiersNum;
+            });
+    ExecutorService executorService = 
Executors.newFixedThreadPool(folderAccessors.size() + 1);
+    CountDownLatch startLatch = new CountDownLatch(1);
+    AtomicBoolean resetting = new AtomicBoolean(true);
+
+    Future<?> resetFuture =
+        executorService.submit(
+            () -> {
+              startLatch.await();
+              try {
+                for (int i = 0; i < RESET_TIMES; i++) {
+                  tierManager.resetFolders();
+                }
+              } finally {
+                resetting.set(false);
+              }
+              return null;
+            });
+    List<AtomicInteger> accessorCounts = new ArrayList<>();
+    List<Future<?>> accessorFutures = new ArrayList<>();
+    for (Callable<?> folderAccessor : folderAccessors) {
+      AtomicInteger accessorCount = new AtomicInteger();
+      accessorCounts.add(accessorCount);
+      accessorFutures.add(
+          executorService.submit(
+              () -> {
+                startLatch.await();
+                do {
+                  assertNotNull(folderAccessor.call());
+                  accessorCount.incrementAndGet();
+                } while (resetting.get());
+                return null;
+              }));
+    }
+
+    startLatch.countDown();
+    try {
+      resetFuture.get(30, TimeUnit.SECONDS);
+      for (Future<?> accessorFuture : accessorFutures) {
+        accessorFuture.get(30, TimeUnit.SECONDS);
+      }
+      for (AtomicInteger accessorCount : accessorCounts) {
+        assertTrue(accessorCount.get() > 0);
+      }
+    } finally {
+      executorService.shutdownNow();
+      assertTrue(executorService.awaitTermination(10, TimeUnit.SECONDS));
+    }
+  }
+}

Reply via email to