Baymine commented on code in PR #66312:
URL: https://github.com/apache/doris/pull/66312#discussion_r3700912042


##########
fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileManager.java:
##########
@@ -602,78 +605,104 @@ protected void 
loadProfilesFromStorageIfFirstTime(boolean sync) {
         if (checkIfProfileLoaded()) {
             return;
         }
+        if (!isProfileLoading.compareAndSet(false, true)) {
+            if (sync) {
+                waitForProfileLoadFinish();
+            }
+            return;
+        }
 
-        // Create a new thread to load profiles
-        Thread loadThread = new Thread(() -> {
+        Runnable loadTask = () -> {
             long startTime = System.currentTimeMillis();
-
+            boolean loadSucceeded = false;
             try {
-                List<String> profileDirAbsPaths = getOnStorageProfileInfos();
-                LOG.info("Reading {} profiles from {}", 
profileDirAbsPaths.size(),
-                        PROFILE_STORAGE_PATH);
-                // Newest profile first
-                profileDirAbsPaths.sort(Collections.reverseOrder());
-
-                // Process profiles in batches
-                for (int i = 0; i < profileDirAbsPaths.size(); i += 
BATCH_SIZE) {
-                    // Thread safe list
-                    List<Profile> profiles = Collections.synchronizedList(new 
ArrayList<>());
-                    int end = Math.min(i + BATCH_SIZE, 
profileDirAbsPaths.size());
-                    List<String> batch = profileDirAbsPaths.subList(i, end);
-
-                    // List of profile io futures for current batch
-                    List<Future<?>> profileIOFutures = Lists.newArrayList();
-
-                    // Create and add tasks for current batch to executor
-                    for (String profileDirAbsPath : batch) {
-                        Thread thread = new Thread(() -> {
-                            Profile profile = Profile.read(profileDirAbsPath);
-                            if (profile != null) {
-                                profiles.add(profile);
-                            }
-                        });
-                        profileIOFutures.add(profileIOExecutor.submit(thread));
-                    }
+                loadProfilesFromStorage();
+                loadSucceeded = true;
+                LOG.info("Load profiles into memory finished, costs {}ms",
+                        System.currentTimeMillis() - startTime);
+            } catch (Exception e) {
+                LOG.error("Failed to load query profile from storage", e);
+            } finally {
+                // Mark loaded even on failure to avoid spawning a new 
profile-loader every second.
+                markProfileLoaded();

Review Comment:
   Good catch, and this was the most important one — fixed in 
d23ac54a95721b2379770c83232b516869636bcb.
   
   `isProfileLoaded` is now the single meaning "the on-disk index is 
complete/authoritative", and it is set (via `markProfileLoaded()`) **only on 
the success path**. A failed or partial load no longer touches it, so 
`deleteBrokenProfiles()` / `deleteOutdatedProfilesFromStorage()` stay disabled 
and cannot delete valid stored profiles that are missing from a partial 
in-memory index. To still prevent loader proliferation on failure, I added a 
separate `AtomicBoolean isProfileLoadFailed` that records the terminal failure 
(WARN: not retried until FE restart) without enabling cleanup.



##########
fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileManager.java:
##########
@@ -602,78 +605,104 @@ protected void 
loadProfilesFromStorageIfFirstTime(boolean sync) {
         if (checkIfProfileLoaded()) {
             return;
         }
+        if (!isProfileLoading.compareAndSet(false, true)) {

Review Comment:
   Fixed in d23ac54a95721b2379770c83232b516869636bcb. After winning the 
`compareAndSet(false, true)`, I recheck `checkIfProfileLoaded() || 
isProfileLoadFailed.get()`; if loading has since terminated I release ownership 
(`isProfileLoading.set(false)`) and return, so a stale pre-CAS read can no 
longer start a second full cold load.



##########
fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileManager.java:
##########
@@ -602,78 +605,104 @@ protected void 
loadProfilesFromStorageIfFirstTime(boolean sync) {
         if (checkIfProfileLoaded()) {
             return;
         }
+        if (!isProfileLoading.compareAndSet(false, true)) {
+            if (sync) {
+                waitForProfileLoadFinish();
+            }
+            return;
+        }
 
-        // Create a new thread to load profiles
-        Thread loadThread = new Thread(() -> {
+        Runnable loadTask = () -> {
             long startTime = System.currentTimeMillis();
-
+            boolean loadSucceeded = false;
             try {
-                List<String> profileDirAbsPaths = getOnStorageProfileInfos();
-                LOG.info("Reading {} profiles from {}", 
profileDirAbsPaths.size(),
-                        PROFILE_STORAGE_PATH);
-                // Newest profile first
-                profileDirAbsPaths.sort(Collections.reverseOrder());
-
-                // Process profiles in batches
-                for (int i = 0; i < profileDirAbsPaths.size(); i += 
BATCH_SIZE) {
-                    // Thread safe list
-                    List<Profile> profiles = Collections.synchronizedList(new 
ArrayList<>());
-                    int end = Math.min(i + BATCH_SIZE, 
profileDirAbsPaths.size());
-                    List<String> batch = profileDirAbsPaths.subList(i, end);
-
-                    // List of profile io futures for current batch
-                    List<Future<?>> profileIOFutures = Lists.newArrayList();
-
-                    // Create and add tasks for current batch to executor
-                    for (String profileDirAbsPath : batch) {
-                        Thread thread = new Thread(() -> {
-                            Profile profile = Profile.read(profileDirAbsPath);
-                            if (profile != null) {
-                                profiles.add(profile);
-                            }
-                        });
-                        profileIOFutures.add(profileIOExecutor.submit(thread));
-                    }
+                loadProfilesFromStorage();
+                loadSucceeded = true;
+                LOG.info("Load profiles into memory finished, costs {}ms",
+                        System.currentTimeMillis() - startTime);
+            } catch (Exception e) {
+                LOG.error("Failed to load query profile from storage", e);
+            } finally {
+                // Mark loaded even on failure to avoid spawning a new 
profile-loader every second.
+                markProfileLoaded();
+                isProfileLoading.set(false);
+                if (!loadSucceeded) {
+                    LOG.warn("Profile loading did not complete successfully, "
+                            + "will not retry until FE restarts. Loaded 
profile count in memory: {}",
+                            queryIdToProfileMap.size());
+                }
+            }
+        };
 
-                    // Wait for all futures in current batch to complete
-                    for (Future<?> future : profileIOFutures) {
-                        try {
-                            future.get();
-                        } catch (Exception e) {
-                            LOG.warn("Failed to read profile from storage", e);
-                        }
-                    }
+        if (sync) {
+            loadTask.run();
+        } else {
+            Thread loadThread = new Thread(loadTask, "profile-loader");
+            loadThread.setDaemon(true);
+            loadThread.start();

Review Comment:
   Fixed in d23ac54a95721b2379770c83232b516869636bcb. The async 
`loadThread.start()` is now wrapped in try/catch: if native-thread allocation 
or `start()` fails, I set `isProfileLoadFailed` and clear `isProfileLoading` 
before returning, so ownership is never stranded and later cycles are not 
permanently blocked (with a WARN that loading won't retry until FE restart).



##########
fe/fe-core/src/test/java/org/apache/doris/common/profile/ProfileManagerTest.java:
##########
@@ -1207,4 +1210,175 @@ void testArchivePendingTimeoutIntegration() throws 
Exception {
             Config.profile_archive_batch_size = originalBatchSize;
         }
     }
+
+    @Test
+    public void testOnlyOneProfileLoaderWhenTriggeredConcurrently() throws 
Exception {
+        int numProfiles = 30;
+        for (int i = 0; i < numProfiles; i++) {
+            UUID taskId = UUID.randomUUID();
+            TUniqueId queryId = new TUniqueId(taskId.getMostSignificantBits(), 
taskId.getLeastSignificantBits());
+            String profileId = DebugUtil.printId(queryId);
+            Profile profile = constructProfile(profileId);
+            profile.writeToStorage(ProfileManager.PROFILE_STORAGE_PATH);
+        }
+
+        profileManager.isProfileLoaded = false;
+
+        AtomicInteger maxProfileLoaderThreads = new AtomicInteger(0);
+        Thread monitorThread = new Thread(() -> {
+            while (!Thread.currentThread().isInterrupted()) {
+                long loaderCount = Thread.getAllStackTraces().keySet().stream()

Review Comment:
   Fixed in d23ac54a95721b2379770c83232b516869636bcb. Replaced the JVM-global 
thread-name sampling with deterministic instrumentation scoped to the tested 
manager: an `AtomicInteger` invocation counter incremented on load entry, plus 
entry/release latches. The winning loader is held inside 
`getOnStorageProfileInfos()` until all 20 trigger calls return, then I assert 
exactly one entry (and re-assert after completion that no second cold load 
starts).



##########
fe/fe-core/src/test/java/org/apache/doris/common/profile/ProfileManagerTest.java:
##########
@@ -1207,4 +1210,175 @@ void testArchivePendingTimeoutIntegration() throws 
Exception {
             Config.profile_archive_batch_size = originalBatchSize;
         }
     }
+
+    @Test
+    public void testOnlyOneProfileLoaderWhenTriggeredConcurrently() throws 
Exception {
+        int numProfiles = 30;
+        for (int i = 0; i < numProfiles; i++) {
+            UUID taskId = UUID.randomUUID();
+            TUniqueId queryId = new TUniqueId(taskId.getMostSignificantBits(), 
taskId.getLeastSignificantBits());
+            String profileId = DebugUtil.printId(queryId);
+            Profile profile = constructProfile(profileId);
+            profile.writeToStorage(ProfileManager.PROFILE_STORAGE_PATH);
+        }
+
+        profileManager.isProfileLoaded = false;
+
+        AtomicInteger maxProfileLoaderThreads = new AtomicInteger(0);
+        Thread monitorThread = new Thread(() -> {
+            while (!Thread.currentThread().isInterrupted()) {
+                long loaderCount = Thread.getAllStackTraces().keySet().stream()
+                        .filter(thread -> 
"profile-loader".equals(thread.getName()))
+                        .count();
+                maxProfileLoaderThreads.updateAndGet(cur -> Math.max(cur, 
(int) loaderCount));
+                try {
+                    Thread.sleep(10);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    return;
+                }
+            }
+        });
+        monitorThread.start();
+
+        int concurrentTriggers = 20;
+        CountDownLatch startLatch = new CountDownLatch(1);
+        Thread[] triggerThreads = new Thread[concurrentTriggers];
+        for (int i = 0; i < concurrentTriggers; i++) {
+            triggerThreads[i] = new Thread(() -> {
+                try {
+                    startLatch.await();
+                    profileManager.loadProfilesFromStorageIfFirstTime(false);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            });
+            triggerThreads[i].start();
+        }
+        startLatch.countDown();
+
+        for (Thread triggerThread : triggerThreads) {
+            triggerThread.join(30000);
+        }
+
+        long waitStart = System.currentTimeMillis();
+        while (!profileManager.isProfileLoaded && System.currentTimeMillis() - 
waitStart < 30000) {
+            Thread.sleep(100);
+        }
+
+        monitorThread.interrupt();
+        monitorThread.join();
+
+        Assertions.assertTrue(profileManager.isProfileLoaded);
+        Assertions.assertEquals(1, maxProfileLoaderThreads.get(),
+                "Only one profile-loader thread should be active at a time");
+        Assertions.assertEquals(numProfiles, 
profileManager.queryIdToProfileMap.size());
+    }
+
+    @Test
+    void testSyncLoadWaitsWhenAsyncLoadInProgress() throws Exception {
+        CountDownLatch loadingEntered = new CountDownLatch(1);
+        CountDownLatch allowComplete = new CountDownLatch(1);
+        ProfileManager pm = new ProfileManager() {
+            @Override
+            protected List<String> getOnStorageProfileInfos() {
+                loadingEntered.countDown();
+                try {
+                    if (!allowComplete.await(30, TimeUnit.SECONDS)) {
+                        throw new RuntimeException("timed out waiting to 
complete profile load");
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+                return super.getOnStorageProfileInfos();
+            }
+        };
+        pm.isProfileLoaded = false;
+
+        Thread asyncLoader = new Thread(() -> 
pm.loadProfilesFromStorageIfFirstTime(false));
+        asyncLoader.start();
+        Assertions.assertTrue(loadingEntered.await(10, TimeUnit.SECONDS));
+
+        long waitStart = System.currentTimeMillis();
+        pm.loadProfilesFromStorageIfFirstTime(true);

Review Comment:
   Fixed in d23ac54a95721b2379770c83232b516869636bcb. The synchronous caller 
now runs on its own thread; while the async load is held I `sleep` and assert 
the sync caller has **not** returned (proving it blocks in 
`waitForProfileLoadFinish()`), then `allowComplete.countDown()`, `join` it, and 
assert it returned successfully. The handoff is now covered without relying on 
the 30s timeout path.



##########
fe/fe-core/src/test/java/org/apache/doris/common/profile/ProfileManagerTest.java:
##########
@@ -1207,4 +1210,175 @@ void testArchivePendingTimeoutIntegration() throws 
Exception {
             Config.profile_archive_batch_size = originalBatchSize;
         }
     }
+
+    @Test
+    public void testOnlyOneProfileLoaderWhenTriggeredConcurrently() throws 
Exception {
+        int numProfiles = 30;
+        for (int i = 0; i < numProfiles; i++) {
+            UUID taskId = UUID.randomUUID();
+            TUniqueId queryId = new TUniqueId(taskId.getMostSignificantBits(), 
taskId.getLeastSignificantBits());
+            String profileId = DebugUtil.printId(queryId);
+            Profile profile = constructProfile(profileId);
+            profile.writeToStorage(ProfileManager.PROFILE_STORAGE_PATH);
+        }
+
+        profileManager.isProfileLoaded = false;
+
+        AtomicInteger maxProfileLoaderThreads = new AtomicInteger(0);
+        Thread monitorThread = new Thread(() -> {
+            while (!Thread.currentThread().isInterrupted()) {
+                long loaderCount = Thread.getAllStackTraces().keySet().stream()
+                        .filter(thread -> 
"profile-loader".equals(thread.getName()))
+                        .count();
+                maxProfileLoaderThreads.updateAndGet(cur -> Math.max(cur, 
(int) loaderCount));
+                try {
+                    Thread.sleep(10);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    return;
+                }
+            }
+        });
+        monitorThread.start();
+
+        int concurrentTriggers = 20;
+        CountDownLatch startLatch = new CountDownLatch(1);
+        Thread[] triggerThreads = new Thread[concurrentTriggers];
+        for (int i = 0; i < concurrentTriggers; i++) {
+            triggerThreads[i] = new Thread(() -> {
+                try {
+                    startLatch.await();
+                    profileManager.loadProfilesFromStorageIfFirstTime(false);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            });
+            triggerThreads[i].start();
+        }
+        startLatch.countDown();
+
+        for (Thread triggerThread : triggerThreads) {
+            triggerThread.join(30000);
+        }
+
+        long waitStart = System.currentTimeMillis();
+        while (!profileManager.isProfileLoaded && System.currentTimeMillis() - 
waitStart < 30000) {
+            Thread.sleep(100);
+        }
+
+        monitorThread.interrupt();
+        monitorThread.join();
+
+        Assertions.assertTrue(profileManager.isProfileLoaded);
+        Assertions.assertEquals(1, maxProfileLoaderThreads.get(),
+                "Only one profile-loader thread should be active at a time");
+        Assertions.assertEquals(numProfiles, 
profileManager.queryIdToProfileMap.size());
+    }
+
+    @Test
+    void testSyncLoadWaitsWhenAsyncLoadInProgress() throws Exception {
+        CountDownLatch loadingEntered = new CountDownLatch(1);
+        CountDownLatch allowComplete = new CountDownLatch(1);
+        ProfileManager pm = new ProfileManager() {
+            @Override
+            protected List<String> getOnStorageProfileInfos() {
+                loadingEntered.countDown();
+                try {
+                    if (!allowComplete.await(30, TimeUnit.SECONDS)) {
+                        throw new RuntimeException("timed out waiting to 
complete profile load");
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+                return super.getOnStorageProfileInfos();
+            }
+        };
+        pm.isProfileLoaded = false;
+
+        Thread asyncLoader = new Thread(() -> 
pm.loadProfilesFromStorageIfFirstTime(false));
+        asyncLoader.start();
+        Assertions.assertTrue(loadingEntered.await(10, TimeUnit.SECONDS));
+
+        long waitStart = System.currentTimeMillis();
+        pm.loadProfilesFromStorageIfFirstTime(true);
+        long waitedMs = System.currentTimeMillis() - waitStart;
+
+        allowComplete.countDown();
+        asyncLoader.join(10000);
+
+        Assertions.assertTrue(pm.isProfileLoaded);
+        Assertions.assertTrue(waitedMs >= 50,
+                "sync load should wait for in-progress async load to finish");
+    }
+
+    @Test
+    void testLoadProfilesFromStorageFailure() throws Exception {
+        Profile profile = constructProfile("fail-load");

Review Comment:
   Fixed in d23ac54a95721b2379770c83232b516869636bcb. The test now uses a valid 
`TUniqueId`-based profile id (`DebugUtil.printId(queryId)`) so 
`parseProfileFileName` accepts the file and `Profile.read()` returns a profile, 
which means the injected `pushProfile` failure is actually reached. I added an 
`AtomicBoolean pushProfileEntered` assertion to prove the override ran, and 
assert `isProfileLoaded` stays **false** on failure (tying this test to the 
M-001 fix).



##########
fe/fe-core/src/test/java/org/apache/doris/common/profile/ProfileManagerTest.java:
##########
@@ -1207,4 +1210,175 @@ void testArchivePendingTimeoutIntegration() throws 
Exception {
             Config.profile_archive_batch_size = originalBatchSize;
         }
     }
+
+    @Test
+    public void testOnlyOneProfileLoaderWhenTriggeredConcurrently() throws 
Exception {
+        int numProfiles = 30;
+        for (int i = 0; i < numProfiles; i++) {
+            UUID taskId = UUID.randomUUID();
+            TUniqueId queryId = new TUniqueId(taskId.getMostSignificantBits(), 
taskId.getLeastSignificantBits());
+            String profileId = DebugUtil.printId(queryId);
+            Profile profile = constructProfile(profileId);
+            profile.writeToStorage(ProfileManager.PROFILE_STORAGE_PATH);
+        }
+
+        profileManager.isProfileLoaded = false;
+
+        AtomicInteger maxProfileLoaderThreads = new AtomicInteger(0);
+        Thread monitorThread = new Thread(() -> {
+            while (!Thread.currentThread().isInterrupted()) {
+                long loaderCount = Thread.getAllStackTraces().keySet().stream()
+                        .filter(thread -> 
"profile-loader".equals(thread.getName()))
+                        .count();
+                maxProfileLoaderThreads.updateAndGet(cur -> Math.max(cur, 
(int) loaderCount));
+                try {
+                    Thread.sleep(10);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    return;
+                }
+            }
+        });
+        monitorThread.start();
+
+        int concurrentTriggers = 20;
+        CountDownLatch startLatch = new CountDownLatch(1);
+        Thread[] triggerThreads = new Thread[concurrentTriggers];
+        for (int i = 0; i < concurrentTriggers; i++) {
+            triggerThreads[i] = new Thread(() -> {
+                try {
+                    startLatch.await();
+                    profileManager.loadProfilesFromStorageIfFirstTime(false);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            });
+            triggerThreads[i].start();
+        }
+        startLatch.countDown();
+
+        for (Thread triggerThread : triggerThreads) {
+            triggerThread.join(30000);
+        }
+
+        long waitStart = System.currentTimeMillis();
+        while (!profileManager.isProfileLoaded && System.currentTimeMillis() - 
waitStart < 30000) {
+            Thread.sleep(100);
+        }
+
+        monitorThread.interrupt();
+        monitorThread.join();
+
+        Assertions.assertTrue(profileManager.isProfileLoaded);
+        Assertions.assertEquals(1, maxProfileLoaderThreads.get(),
+                "Only one profile-loader thread should be active at a time");
+        Assertions.assertEquals(numProfiles, 
profileManager.queryIdToProfileMap.size());
+    }
+
+    @Test
+    void testSyncLoadWaitsWhenAsyncLoadInProgress() throws Exception {
+        CountDownLatch loadingEntered = new CountDownLatch(1);
+        CountDownLatch allowComplete = new CountDownLatch(1);
+        ProfileManager pm = new ProfileManager() {
+            @Override
+            protected List<String> getOnStorageProfileInfos() {
+                loadingEntered.countDown();
+                try {
+                    if (!allowComplete.await(30, TimeUnit.SECONDS)) {
+                        throw new RuntimeException("timed out waiting to 
complete profile load");
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+                return super.getOnStorageProfileInfos();
+            }
+        };
+        pm.isProfileLoaded = false;
+
+        Thread asyncLoader = new Thread(() -> 
pm.loadProfilesFromStorageIfFirstTime(false));
+        asyncLoader.start();
+        Assertions.assertTrue(loadingEntered.await(10, TimeUnit.SECONDS));
+
+        long waitStart = System.currentTimeMillis();
+        pm.loadProfilesFromStorageIfFirstTime(true);
+        long waitedMs = System.currentTimeMillis() - waitStart;
+
+        allowComplete.countDown();
+        asyncLoader.join(10000);
+
+        Assertions.assertTrue(pm.isProfileLoaded);
+        Assertions.assertTrue(waitedMs >= 50,
+                "sync load should wait for in-progress async load to finish");
+    }
+
+    @Test
+    void testLoadProfilesFromStorageFailure() throws Exception {
+        Profile profile = constructProfile("fail-load");
+        profile.writeToStorage(ProfileManager.PROFILE_STORAGE_PATH);
+
+        ProfileManager pm = new ProfileManager() {
+            @Override
+            public void pushProfile(Profile profile) {
+                throw new RuntimeException("simulated load failure");
+            }
+        };
+        pm.isProfileLoaded = false;
+
+        pm.loadProfilesFromStorageIfFirstTime(true);
+
+        Assertions.assertTrue(pm.isProfileLoaded);
+        Assertions.assertTrue(pm.queryIdToProfileMap.isEmpty());
+    }
+
+    @Test
+    void testReadProfileIOExceptionInBatch() throws Exception {
+        ProfileManager pm = new ProfileManager();
+        pm.isProfileLoaded = false;
+
+        // Malformed (non-zip) profile files: real Profile.read tolerates them 
and returns null,
+        // so the batch load must still finish and mark isProfileLoaded=true 
without loading any.
+        for (int i = 0; i < 3; i++) {
+            File profileFile = new File(tempDir, System.currentTimeMillis() + 
"_badprofile" + i);
+            profileFile.createNewFile();
+        }
+
+        pm.loadProfilesFromStorageIfFirstTime(true);
+
+        Assertions.assertTrue(pm.isProfileLoaded);
+        Assertions.assertEquals(0, pm.queryIdToProfileMap.size());
+    }
+
+    @Test
+    void testWaitForProfileLoadInterrupted() throws Exception {
+        CountDownLatch loadingEntered = new CountDownLatch(1);
+        CountDownLatch holdLoad = new CountDownLatch(1);
+        ProfileManager pm = new ProfileManager() {
+            @Override
+            protected List<String> getOnStorageProfileInfos() {
+                loadingEntered.countDown();
+                try {
+                    holdLoad.await(30, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+                return super.getOnStorageProfileInfos();
+            }
+        };
+        pm.isProfileLoaded = false;
+
+        Thread asyncLoader = new Thread(() -> 
pm.loadProfilesFromStorageIfFirstTime(false));
+        asyncLoader.start();
+        Assertions.assertTrue(loadingEntered.await(10, TimeUnit.SECONDS));
+
+        Thread waiterThread = new Thread(() -> 
pm.loadProfilesFromStorageIfFirstTime(true));
+        waiterThread.start();
+        Thread.sleep(200);
+        waiterThread.interrupt();
+        waiterThread.join(5000);
+
+        Assertions.assertFalse(waiterThread.isAlive());
+
+        holdLoad.countDown();
+        asyncLoader.join(10000);

Review Comment:
   Fixed in d23ac54a95721b2379770c83232b516869636bcb. Added a `loaderFinished` 
latch that the overridden `getOnStorageProfileInfos()` counts down in a 
`finally`. After `holdLoad.countDown()` the test awaits `loaderFinished` and 
then the loader's terminal `isProfileLoaded` state before returning, so 
`@AfterEach` can't restore the storage path / delete the temp dir underneath an 
in-flight loader.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to