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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new bf4fdf900ca branch-4.1: [Fix](profile) Prevent concurrent historical 
profile loaders (#67012) (#67069)
bf4fdf900ca is described below

commit bf4fdf900ca28c6f5f61c7dcd1d81a451c7b7fc4
Author: linrrarity <[email protected]>
AuthorDate: Mon Sep 7 18:59:35 2026 +0800

    branch-4.1: [Fix](profile) Prevent concurrent historical profile loaders 
(#67012) (#67069)
    
    pick: https://github.com/apache/doris/pull/67012
---
 .../doris/common/profile/ProfileManager.java       | 50 +++++++--------
 .../doris/common/profile/ProfileManagerTest.java   | 75 ++++++++++++++++++++--
 2 files changed, 91 insertions(+), 34 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileManager.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileManager.java
index b9b30fff647..731e7997e0a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileManager.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileManager.java
@@ -56,6 +56,7 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
@@ -122,10 +123,14 @@ public class ProfileManager extends MasterDaemon {
         }
     }
 
-    // this variable is assigned to true the first time the profile is loaded 
from storage
-    // no further write operation, so no data race
-    private final ReentrantReadWriteLock isProfileLoadedLock = new 
ReentrantReadWriteLock();
-    volatile boolean isProfileLoaded = false;
+    enum ProfileLoadStatus {
+        UNLOADED,
+        LOADING,
+        LOADED
+    }
+
+    final AtomicReference<ProfileLoadStatus> profileLoadStatus =
+            new AtomicReference<>(ProfileLoadStatus.UNLOADED);
 
     // only protect queryIdDeque; queryIdToProfileMap is concurrent, no need 
to protect
     private ReentrantReadWriteLock lock;
@@ -551,21 +556,21 @@ public class ProfileManager extends MasterDaemon {
     // string will contain profile id and its storage timestamp
     protected List<String> getOnStorageProfileInfos() {
         List<String> res = Lists.newArrayList();
-        try {
-            File profileDir = new File(PROFILE_STORAGE_PATH);
-            if (!profileDir.exists()) {
-                LOG.warn("Profile storage directory {} does not exist", 
PROFILE_STORAGE_PATH);
-                return res;
-            }
+        File profileDir = new File(PROFILE_STORAGE_PATH);
+        if (!profileDir.exists()) {
+            LOG.warn("Profile storage directory {} does not exist", 
PROFILE_STORAGE_PATH);
+            return res;
+        }
 
-            File[] files = profileDir.listFiles();
+        File[] files = profileDir.listFiles();
+        if (files != null) {
             for (File file : files) {
                 if (file.isFile()) {
                     res.add(file.getAbsolutePath());
                 }
             }
-        } catch (Exception e) {
-            LOG.error("Failed to get profile meta from storage", e);
+        } else {
+            throw new IllegalStateException("Failed to list profile storage 
directory: " + PROFILE_STORAGE_PATH);
         }
 
         return res;
@@ -575,7 +580,7 @@ public class ProfileManager extends MasterDaemon {
     // deserialize to an object Profile
     // push them to memory structure of ProfileManager for index
     protected void loadProfilesFromStorageIfFirstTime(boolean sync) {
-        if (checkIfProfileLoaded()) {
+        if (!profileLoadStatus.compareAndSet(ProfileLoadStatus.UNLOADED, 
ProfileLoadStatus.LOADING)) {
             return;
         }
 
@@ -629,15 +634,11 @@ public class ProfileManager extends MasterDaemon {
 
                 LOG.info("Load profiles into memory finished, costs {}ms", 
System.currentTimeMillis() - startTime);
 
-                // Set isProfileLoaded to true with write lock
-                isProfileLoadedLock.writeLock().lock();
-                try {
-                    this.isProfileLoaded = true;
-                } finally {
-                    isProfileLoadedLock.writeLock().unlock();
-                }
+                profileLoadStatus.set(ProfileLoadStatus.LOADED);
             } catch (Exception e) {
                 LOG.error("Failed to load query profile from storage", e);
+            } finally {
+                profileLoadStatus.compareAndSet(ProfileLoadStatus.LOADING, 
ProfileLoadStatus.UNLOADED);
             }
         });
 
@@ -1099,12 +1100,7 @@ public class ProfileManager extends MasterDaemon {
     }
 
     private boolean checkIfProfileLoaded() {
-        isProfileLoadedLock.readLock().lock();
-        try {
-            return isProfileLoaded;
-        } finally {
-            isProfileLoadedLock.readLock().unlock();
-        }
+        return profileLoadStatus.get() == ProfileLoadStatus.LOADED;
     }
 
     public void removeProfile(String profileId) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/common/profile/ProfileManagerTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/common/profile/ProfileManagerTest.java
index 21096556a8f..65778b8c512 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/common/profile/ProfileManagerTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/common/profile/ProfileManagerTest.java
@@ -19,6 +19,7 @@ package org.apache.doris.common.profile;
 
 import org.apache.doris.common.Config;
 import org.apache.doris.common.profile.ProfileManager.ProfileElement;
+import org.apache.doris.common.profile.ProfileManager.ProfileLoadStatus;
 import org.apache.doris.common.util.DebugUtil;
 import org.apache.doris.thrift.TUniqueId;
 
@@ -44,7 +45,10 @@ import java.util.PriorityQueue;
 import java.util.Random;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 @ResourceLock("global")
 class ProfileManagerTest {
@@ -66,7 +70,7 @@ class ProfileManagerTest {
         originalPath = ProfileManager.PROFILE_STORAGE_PATH;
         ProfileManager.PROFILE_STORAGE_PATH = tempDir.getAbsolutePath();
         profileManager.cleanProfile();
-        profileManager.isProfileLoaded = false;
+        profileManager.profileLoadStatus.set(ProfileLoadStatus.UNLOADED);
         originMaxProfiles = Config.max_query_profile_num;
     }
 
@@ -370,7 +374,7 @@ class ProfileManagerTest {
 
     @Test
     void testLoadProfile() throws IOException {
-        profileManager.isProfileLoaded = false;
+        profileManager.profileLoadStatus.set(ProfileLoadStatus.UNLOADED);
 
         try {
             // Create some test profile files
@@ -381,7 +385,7 @@ class ProfileManagerTest {
             }
 
             profileManager.loadProfilesFromStorageIfFirstTime(true);
-            Assertions.assertTrue(profileManager.isProfileLoaded);
+            Assertions.assertEquals(ProfileLoadStatus.LOADED, 
profileManager.profileLoadStatus.get());
             Assertions.assertEquals(30, 
profileManager.queryIdToProfileMap.size());
             Assertions.assertEquals(0, 
profileManager.queryIdToExecutionProfiles.size());
         } catch (InterruptedException e) {
@@ -548,7 +552,7 @@ class ProfileManagerTest {
             }
 
             // Execute deletion
-            profileManager.isProfileLoaded = true;
+            profileManager.profileLoadStatus.set(ProfileLoadStatus.LOADED);
             profileManager.deleteOutdatedProfilesFromStorage();
 
             // Verify correct profiles were deleted
@@ -626,7 +630,7 @@ class ProfileManagerTest {
         }
 
         // Delete broken profiles
-        profileManager.isProfileLoaded = true;
+        profileManager.profileLoadStatus.set(ProfileLoadStatus.LOADED);
         profileManager.deleteBrokenProfiles();
 
         // Verify normal files still exist
@@ -661,6 +665,63 @@ class ProfileManagerTest {
         Assertions.assertEquals(numProfiles, 
profileManager.queryIdToProfileMap.size());
     }
 
+    @Test
+    public void testOnlyOneProfileLoaderCanRun() throws Exception {
+        CountDownLatch loadStarted = new CountDownLatch(1);
+        CountDownLatch duplicateLoadStarted = new CountDownLatch(1);
+        CountDownLatch allowLoadToFinish = new CountDownLatch(1);
+        AtomicInteger scanCount = new AtomicInteger();
+        ProfileManager manager = new ProfileManager() {
+            @Override
+            protected List<String> getOnStorageProfileInfos() {
+                if (scanCount.incrementAndGet() > 1) {
+                    duplicateLoadStarted.countDown();
+                }
+                loadStarted.countDown();
+                try {
+                    allowLoadToFinish.await();
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    throw new RuntimeException(e);
+                }
+                return Lists.newArrayList();
+            }
+        };
+        Thread initialLoad = new Thread(() -> 
manager.loadProfilesFromStorageIfFirstTime(true));
+
+        try {
+            initialLoad.start();
+            Assertions.assertTrue(loadStarted.await(5, TimeUnit.SECONDS));
+
+            for (int i = 0; i < 10; i++) {
+                manager.loadProfilesFromStorageIfFirstTime(false);
+            }
+
+            Assertions.assertFalse(duplicateLoadStarted.await(500, 
TimeUnit.MILLISECONDS));
+            Assertions.assertEquals(1, scanCount.get());
+        } finally {
+            allowLoadToFinish.countDown();
+            initialLoad.join(5000);
+            Assertions.assertFalse(initialLoad.isAlive());
+        }
+    }
+
+    @Test
+    public void testProfileLoaderCanRetryAfterFailure() throws IOException {
+        File invalidProfileStorage = new File(tempDir, "not_a_directory");
+        Assertions.assertTrue(invalidProfileStorage.createNewFile());
+        ProfileManager.PROFILE_STORAGE_PATH = 
invalidProfileStorage.getAbsolutePath();
+        ProfileManager manager = new ProfileManager();
+
+        manager.loadProfilesFromStorageIfFirstTime(true);
+        Assertions.assertEquals(ProfileLoadStatus.UNLOADED, 
manager.profileLoadStatus.get());
+
+        Assertions.assertTrue(invalidProfileStorage.delete());
+        ProfileManager.PROFILE_STORAGE_PATH = tempDir.getAbsolutePath();
+        manager.loadProfilesFromStorageIfFirstTime(true);
+        Assertions.assertEquals(ProfileLoadStatus.LOADED, 
manager.profileLoadStatus.get());
+    }
+
     @Test
     public void testProfileStorageLimit() throws Exception {
         // Set small storage limit
@@ -681,7 +742,7 @@ class ProfileManagerTest {
         }
 
         // Trigger cleanup
-        profileManager.isProfileLoaded = true;
+        profileManager.profileLoadStatus.set(ProfileLoadStatus.LOADED);
         profileManager.deleteOutdatedProfilesFromStorage();
 
         // Verify number of profiles is within limits
@@ -706,7 +767,7 @@ class ProfileManagerTest {
         brokenFile.createNewFile();
 
         // Trigger cleanup
-        profileManager.isProfileLoaded = true;
+        profileManager.profileLoadStatus.set(ProfileLoadStatus.LOADED);
         profileManager.deleteBrokenProfiles();
 
         // Verify broken profile is removed but valid one remains


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

Reply via email to