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]