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 feba0c62b8a feat(load): reject active load listening dirs under data
directory (#18405)
feba0c62b8a is described below
commit feba0c62b8a4e0542bb514740c42de6c49f966a3
Author: Zhenyu Luo <[email protected]>
AuthorDate: Thu Aug 6 19:02:52 2026 +0800
feat(load): reject active load listening dirs under data directory (#18405)
Skip invalid load_active_listening_dirs and load_active_listening_pipe_dir
updates when they fall under the data directory, log a warning with the
retained configuration, and ignore such dirs during hot reload scanning.
---
.../apache/iotdb/db/i18n/DataNodeMiscMessages.java | 8 ++
.../iotdb/db/i18n/StorageEngineMessages.java | 6 +
.../apache/iotdb/db/i18n/DataNodeMiscMessages.java | 6 +
.../iotdb/db/i18n/StorageEngineMessages.java | 4 +
.../java/org/apache/iotdb/db/conf/IoTDBConfig.java | 63 +++++++++-
.../load/active/ActiveLoadDirScanner.java | 29 ++++-
.../db/conf/ActiveLoadListeningDirConfigTest.java | 139 +++++++++++++++++++++
7 files changed, 249 insertions(+), 6 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
index f3e8154dcde..0853e59b9fa 100644
---
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
@@ -1509,5 +1509,13 @@ public final class DataNodeMiscMessages {
public static final String
EXCEPTION_CONTINUOUS_QUERY_MIN_EVERY_INTERVAL_IN_MS_SHOULD_BE_GREATER_THAN_0_BUT_CURRENT_VALUE_IS_F9A1BEC4
= "continuous_query_min_every_interval_in_ms should be greater than 0, but
current value is ";
public static final String
EXCEPTION_UNKNOWN_READ_CONSISTENCY_LEVEL_ARG_PLEASE_SET_TO_STRONG_OR_WEAK_8CF29949
= "Unknown read_consistency_level: %s, please set to \"strong\" or \"weak\"";
public static final String
MESSAGE_INITIAL_ALLOCATEMEMORYFORAUTORESIZINGBUFFER_ARG_A0DB6DA0 = "initial
allocateMemoryForAutoResizingBuffer = {}";
+ public static final String
LOG_SKIP_SETTING_ARG_TO_ARG_BECAUSE_IT_IS_UNDER_DATA_DIRECTORY_KEEP_USING_ORIGINAL_CONFIGURATION_EE87FFD9
=
+ "Skip setting {} to {} because it is under data directory. Keep using
the original "
+ + "configuration: {}.";
+ public static final String
LOG_SKIP_SETTING_ARG_TO_ARG_BECAUSE_ITS_CANONICAL_PATH_CANNOT_BE_RESOLVED_ARG_KEEP_USING_ORIGINAL_CONFIGURATION_C0A8ED09
=
+ "Skip setting {} to {} because its canonical path cannot be resolved:
{}. Keep using the "
+ + "original configuration: {}.";
+ public static final String
MISC_EXCEPTION_FAILED_TO_RESOLVE_CANONICAL_PATH_FOR_ACTIVE_LOAD_LISTENING_DIRECTORY_S_ARG_0E6A508E
=
+ "Failed to resolve canonical path for active load listening directory
%s: %s";
}
diff --git
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/StorageEngineMessages.java
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/StorageEngineMessages.java
index ffa6c918d48..a104bf3576c 100644
---
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/StorageEngineMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/StorageEngineMessages.java
@@ -1227,6 +1227,12 @@ public final class StorageEngineMessages {
+ "File will be moved to fail directory.";
public static final String
STORAGE_LOG_ERROR_OCCURRED_DURING_HOT_RELOAD_ACTIVE_LOAD_DIRS_CURRENT_673AFC0F =
"Error occurred during hot reload active load dirs. Current active load
listening dirs: {}.";
+ public static final String
LOG_ACTIVE_LOAD_LISTENING_DIRECTORY_S_IS_SKIPPED_DURING_HOT_RELOAD_BECAUSE_IT_IS_UNDER_IOTDB_DATA_DIRECTORY_DA90CAE1
=
+ "Active load listening directory {} is skipped during hot reload because
it is under data "
+ + "directory.";
+ public static final String
LOG_FAILED_TO_VALIDATE_ACTIVE_LOAD_LISTENING_DIRECTORY_S_SKIP_SCANNING_ARG_0E6A508E
=
+ "Failed to validate active load listening directory {}. Skip scanning
this directory. "
+ + "Reason: {}";
public static final String
STORAGE_LOG_CURRENT_DIR_PATH_IS_NOT_READABLE_SKIP_SCANNING_THIS_DIR_9C8B7E00 =
"Current dir path is not readable: {}.Skip scanning this dir. Please
check the permission.";
public static final String
STORAGE_LOG_CURRENT_DIR_PATH_IS_NOT_WRITABLE_SKIP_SCANNING_THIS_DIR_4885E78F =
diff --git
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
index 5dd49c8804e..1abc4bb25dc 100644
---
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
@@ -1487,5 +1487,11 @@ public final class DataNodeMiscMessages {
public static final String
EXCEPTION_CONTINUOUS_QUERY_MIN_EVERY_INTERVAL_IN_MS_SHOULD_BE_GREATER_THAN_0_BUT_CURRENT_VALUE_IS_F9A1BEC4
= "continuous_query_min_every_interval_in_ms 必须大于 0,但当前值为 ";
public static final String
EXCEPTION_UNKNOWN_READ_CONSISTENCY_LEVEL_ARG_PLEASE_SET_TO_STRONG_OR_WEAK_8CF29949
= "未知的 read_consistency_level:%s,请设置为 \"strong\" 或 \"weak\"";
public static final String
MESSAGE_INITIAL_ALLOCATEMEMORYFORAUTORESIZINGBUFFER_ARG_A0DB6DA0 = "初始
allocateMemoryForAutoResizingBuffer = {}";
+ public static final String
LOG_SKIP_SETTING_ARG_TO_ARG_BECAUSE_IT_IS_UNDER_DATA_DIRECTORY_KEEP_USING_ORIGINAL_CONFIGURATION_EE87FFD9
=
+ "跳过设置 {} 为 {},因为其位于 data 目录下。继续使用原配置:{}。";
+ public static final String
LOG_SKIP_SETTING_ARG_TO_ARG_BECAUSE_ITS_CANONICAL_PATH_CANNOT_BE_RESOLVED_ARG_KEEP_USING_ORIGINAL_CONFIGURATION_C0A8ED09
=
+ "跳过设置 {} 为 {},因为无法解析其 canonical 路径:{}。继续使用原配置:{}。";
+ public static final String
MISC_EXCEPTION_FAILED_TO_RESOLVE_CANONICAL_PATH_FOR_ACTIVE_LOAD_LISTENING_DIRECTORY_S_ARG_0E6A508E
=
+ "无法解析 Active Load 监听目录 %s 的 canonical 路径:%s";
}
diff --git
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/StorageEngineMessages.java
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/StorageEngineMessages.java
index e30108f7a26..ba857ecfa91 100644
---
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/StorageEngineMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/StorageEngineMessages.java
@@ -1171,6 +1171,10 @@ public final class StorageEngineMessages {
"自动加载 TsFile {} (isGeneratedByPipe = {}) 失败,原因:发生未知异常。文件将被移动到失败目录。";
public static final String
STORAGE_LOG_ERROR_OCCURRED_DURING_HOT_RELOAD_ACTIVE_LOAD_DIRS_CURRENT_673AFC0F =
"热重载 active load 目录时发生错误。当前 active load 监听目录:{}。";
+ public static final String
LOG_ACTIVE_LOAD_LISTENING_DIRECTORY_S_IS_SKIPPED_DURING_HOT_RELOAD_BECAUSE_IT_IS_UNDER_IOTDB_DATA_DIRECTORY_DA90CAE1
=
+ "Active Load 监听目录 {} 位于 data 目录下,热重载时将跳过该目录。";
+ public static final String
LOG_FAILED_TO_VALIDATE_ACTIVE_LOAD_LISTENING_DIRECTORY_S_SKIP_SCANNING_ARG_0E6A508E
=
+ "无法校验 Active Load 监听目录 {},将跳过扫描该目录。原因:{}";
public static final String
STORAGE_LOG_CURRENT_DIR_PATH_IS_NOT_READABLE_SKIP_SCANNING_THIS_DIR_9C8B7E00 =
"当前目录路径不可读:{}。跳过扫描该目录。请检查权限。";
public static final String
STORAGE_LOG_CURRENT_DIR_PATH_IS_NOT_WRITABLE_SKIP_SCANNING_THIS_DIR_4885E78F =
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
index 19c3cc6217f..1d726366ddd 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
@@ -1570,6 +1570,50 @@ public class IoTDBConfig {
return internalDataDirCanonicalPaths.getPaths();
}
+ public boolean isUnderInternalDataDir(final String dirPath) {
+ try {
+ final Path sourcePath = new File(dirPath).getCanonicalFile().toPath();
+ for (final Path internalDataDirCanonicalPath :
getInternalDataDirCanonicalPaths()) {
+ if (sourcePath.startsWith(internalDataDirCanonicalPath)
+ || internalDataDirCanonicalPath.startsWith(sourcePath)) {
+ return true;
+ }
+ }
+ return false;
+ } catch (final Exception e) {
+ return true;
+ }
+ }
+
+ private static final String LOAD_ACTIVE_LISTENING_DIRS_CONFIG_KEY =
"load_active_listening_dirs";
+ private static final String LOAD_ACTIVE_LISTENING_PIPE_DIR_CONFIG_KEY =
+ "load_active_listening_pipe_dir";
+
+ private boolean tryAcceptActiveLoadListeningDir(
+ final String configKey, final String dirPath, final String
currentConfigValue) {
+ try {
+ if (isUnderInternalDataDir(dirPath)) {
+ logger.warn(
+ DataNodeMiscMessages
+
.LOG_SKIP_SETTING_ARG_TO_ARG_BECAUSE_IT_IS_UNDER_DATA_DIRECTORY_KEEP_USING_ORIGINAL_CONFIGURATION_EE87FFD9,
+ configKey,
+ dirPath,
+ currentConfigValue);
+ return false;
+ }
+ return true;
+ } catch (final IllegalArgumentException e) {
+ logger.warn(
+ DataNodeMiscMessages
+
.LOG_SKIP_SETTING_ARG_TO_ARG_BECAUSE_ITS_CANONICAL_PATH_CANNOT_BE_RESOLVED_ARG_KEEP_USING_ORIGINAL_CONFIGURATION_C0A8ED09,
+ configKey,
+ dirPath,
+ e.getMessage(),
+ currentConfigValue);
+ return false;
+ }
+ }
+
public String[][] getTierDataDirs() {
return tierDataDirs;
}
@@ -4246,7 +4290,14 @@ public class IoTDBConfig {
}
public void setLoadActiveListeningPipeDir(String loadActiveListeningPipeDir)
{
- this.loadActiveListeningPipeDir =
addDataHomeDir(loadActiveListeningPipeDir);
+ final String normalizedDir = addDataHomeDir(loadActiveListeningPipeDir);
+ if (!tryAcceptActiveLoadListeningDir(
+ LOAD_ACTIVE_LISTENING_PIPE_DIR_CONFIG_KEY,
+ normalizedDir,
+ getLoadActiveListeningPipeDir())) {
+ return;
+ }
+ this.loadActiveListeningPipeDir = normalizedDir;
}
public String[] getLoadActiveListeningDirs() {
@@ -4263,10 +4314,16 @@ public class IoTDBConfig {
}
public void setLoadActiveListeningDirs(String[] loadActiveListeningDirs) {
+ final String currentConfigValue =
Arrays.toString(getLoadActiveListeningDirs());
+ final String[] normalizedDirs = new String[loadActiveListeningDirs.length];
for (int i = 0; i < loadActiveListeningDirs.length; i++) {
- loadActiveListeningDirs[i] = addDataHomeDir(loadActiveListeningDirs[i]);
+ normalizedDirs[i] = addDataHomeDir(loadActiveListeningDirs[i]);
+ if (!tryAcceptActiveLoadListeningDir(
+ LOAD_ACTIVE_LISTENING_DIRS_CONFIG_KEY, normalizedDirs[i],
currentConfigValue)) {
+ return;
+ }
}
- this.loadActiveListeningDirs = loadActiveListeningDirs;
+ this.loadActiveListeningDirs = normalizedDirs;
}
public boolean getLoadActiveListeningEnable() {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/active/ActiveLoadDirScanner.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/active/ActiveLoadDirScanner.java
index 9d1ad235ecb..31f2bd9dd23 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/active/ActiveLoadDirScanner.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/active/ActiveLoadDirScanner.java
@@ -40,7 +40,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -199,7 +198,9 @@ public class ActiveLoadDirScanner extends
ActiveLoadScheduledExecutorService {
listeningDirs.clear();
listeningDirsConfig.set(IOTDB_CONFIG.getLoadActiveListeningDirs());
-
listeningDirs.addAll(Arrays.asList(IOTDB_CONFIG.getLoadActiveListeningDirs()));
+ for (final String dir :
IOTDB_CONFIG.getLoadActiveListeningDirs()) {
+ addActiveLoadListeningDirIfAllowed(dir);
+ }
LoadUtil.updateLoadDiskSelector();
}
}
@@ -221,7 +222,7 @@ public class ActiveLoadDirScanner extends
ActiveLoadScheduledExecutorService {
}
// Active load is always enabled for pipe data sync.
- listeningDirs.add(IOTDB_CONFIG.getLoadActiveListeningPipeDir());
+
addActiveLoadListeningDirIfAllowed(IOTDB_CONFIG.getLoadActiveListeningPipeDir());
// Create directories if not exists
listeningDirs.forEach(this::createDirectoriesIfNotExists);
@@ -245,6 +246,28 @@ public class ActiveLoadDirScanner extends
ActiveLoadScheduledExecutorService {
}
}
+ private void addActiveLoadListeningDirIfAllowed(final String dirPath) {
+ if (dirPath == null || dirPath.isEmpty()) {
+ return;
+ }
+ try {
+ if (IOTDB_CONFIG.isUnderInternalDataDir(dirPath)) {
+ LOGGER.warn(
+ StorageEngineMessages
+
.LOG_ACTIVE_LOAD_LISTENING_DIRECTORY_S_IS_SKIPPED_DURING_HOT_RELOAD_BECAUSE_IT_IS_UNDER_IOTDB_DATA_DIRECTORY_DA90CAE1,
+ dirPath);
+ return;
+ }
+ listeningDirs.add(dirPath);
+ } catch (final IllegalArgumentException e) {
+ LOGGER.warn(
+ StorageEngineMessages
+
.LOG_FAILED_TO_VALIDATE_ACTIVE_LOAD_LISTENING_DIRECTORY_S_SKIP_SCANNING_ARG_0E6A508E,
+ dirPath,
+ e.getMessage());
+ }
+ }
+
// Metrics
public long countAndReportActiveListeningDirsFileNumber() {
long totalFileCount = 0;
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/conf/ActiveLoadListeningDirConfigTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/conf/ActiveLoadListeningDirConfigTest.java
new file mode 100644
index 00000000000..e78008719c2
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/conf/ActiveLoadListeningDirConfigTest.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.conf;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.stream.Stream;
+
+public class ActiveLoadListeningDirConfigTest {
+
+ // Reject listening dirs under data/ and keep the original config; dirs
under ext/load/ are
+ // allowed.
+ @Test
+ public void testActiveLoadListeningDirUnderDataDirIsSkipped() throws
Exception {
+ final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig();
+ final String[][] originalTierDataDirs = config.getTierDataDirs();
+ final String[] originalListeningDirs =
config.getLoadActiveListeningDirs().clone();
+ final String originalPipeListeningDir =
config.getLoadActiveListeningPipeDir();
+ final Path dataNodeDir =
Files.createTempDirectory("active-load-listening-config");
+ final Path dataDir = dataNodeDir.resolve("data");
+ final Path allowedDir =
dataNodeDir.resolve("ext").resolve("load").resolve("pending");
+ Files.createDirectories(allowedDir);
+
+ try {
+ config.setTierDataDirs(new String[][] {{dataDir.toString()}});
+
+ config.setLoadActiveListeningDirs(new String[]
{dataDir.resolve("pending").toString()});
+ Assert.assertArrayEquals(originalListeningDirs,
config.getLoadActiveListeningDirs());
+
+ config.setLoadActiveListeningPipeDir(dataDir.resolve("pipe").toString());
+ Assert.assertEquals(originalPipeListeningDir,
config.getLoadActiveListeningPipeDir());
+
+ config.setLoadActiveListeningDirs(new String[] {allowedDir.toString()});
+ config.setLoadActiveListeningPipeDir(
+
dataNodeDir.resolve("ext").resolve("load").resolve("pipe").toString());
+ Assert.assertFalse(config.isUnderInternalDataDir(allowedDir.toString()));
+ } finally {
+ config.setTierDataDirs(originalTierDataDirs);
+ config.setLoadActiveListeningDirs(originalListeningDirs);
+ config.setLoadActiveListeningPipeDir(originalPipeListeningDir);
+ deleteRecursively(dataNodeDir);
+ }
+ }
+
+ // Reject the data directory itself as load_active_listening_dirs and keep
the original config.
+ @Test
+ public void testDataDirectoryItselfIsSkippedAsActiveLoadListeningDir()
throws Exception {
+ final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig();
+ final String[][] originalTierDataDirs = config.getTierDataDirs();
+ final String[] originalListeningDirs =
config.getLoadActiveListeningDirs().clone();
+ final Path dataNodeDir =
Files.createTempDirectory("active-load-listening-data-root");
+ final Path dataDir = Files.createDirectories(dataNodeDir.resolve("data"));
+
+ try {
+ config.setTierDataDirs(new String[][] {{dataDir.toString()}});
+
+ config.setLoadActiveListeningDirs(new String[] {dataDir.toString()});
+ Assert.assertArrayEquals(originalListeningDirs,
config.getLoadActiveListeningDirs());
+ } finally {
+ config.setTierDataDirs(originalTierDataDirs);
+ config.setLoadActiveListeningDirs(originalListeningDirs);
+ deleteRecursively(dataNodeDir);
+ }
+ }
+
+ // Invalid data-dir updates must not overwrite previously accepted
pending/pipe listening dirs.
+ @Test
+ public void
testInvalidActiveLoadListeningConfigDoesNotOverwriteExistingValue() throws
Exception {
+ final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig();
+ final String[][] originalTierDataDirs = config.getTierDataDirs();
+ final String[] originalListeningDirs =
config.getLoadActiveListeningDirs().clone();
+ final String originalPipeListeningDir =
config.getLoadActiveListeningPipeDir();
+ final Path dataNodeDir =
Files.createTempDirectory("active-load-listening-rollback");
+ final Path dataDir = dataNodeDir.resolve("data");
+ final Path allowedDir =
dataNodeDir.resolve("ext").resolve("load").resolve("pending");
+ final Path allowedPipeDir =
dataNodeDir.resolve("ext").resolve("load").resolve("pipe");
+ Files.createDirectories(allowedDir);
+ Files.createDirectories(allowedPipeDir);
+
+ try {
+ config.setTierDataDirs(new String[][] {{dataDir.toString()}});
+ config.setLoadActiveListeningDirs(new String[] {allowedDir.toString()});
+ config.setLoadActiveListeningPipeDir(allowedPipeDir.toString());
+
+ config.setLoadActiveListeningPipeDir(dataDir.resolve("pipe").toString());
+ Assert.assertEquals(allowedPipeDir.toString(),
config.getLoadActiveListeningPipeDir());
+
+ config.setLoadActiveListeningDirs(new String[]
{dataDir.resolve("pending").toString()});
+ Assert.assertArrayEquals(
+ new String[] {allowedDir.toString()},
config.getLoadActiveListeningDirs());
+ } finally {
+ config.setTierDataDirs(originalTierDataDirs);
+ config.setLoadActiveListeningDirs(originalListeningDirs);
+ config.setLoadActiveListeningPipeDir(originalPipeListeningDir);
+ deleteRecursively(dataNodeDir);
+ }
+ }
+
+ private static void deleteRecursively(final Path path) throws IOException {
+ if (path == null || !Files.exists(path)) {
+ return;
+ }
+
+ try (final Stream<Path> pathStream = Files.walk(path)) {
+ pathStream
+ .sorted(Comparator.reverseOrder())
+ .forEach(
+ currentPath -> {
+ try {
+ Files.deleteIfExists(currentPath);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+ }
+}