From 3c257889644a460d225275588a0165bd43979168 Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Fri, 24 Jul 2026 18:37:16 +0800 Subject: [PATCH 1/4] Add CorruptedTsFileException for TsFile corruption errors with file path during query execution - Introduce CorruptedTsFileException extending RuntimeException with Stage enum (READ_TIMESERIES_METADATA, READ_CHUNK_DATA, LOAD_PAGE_READER, DECODE_PAGE_DATA, READ_METADATA_INDEX_NODE) - Uses super(message) + addSuppressed(cause) to prevent getRootCause penetration - Carries File reference and Stage for precise error reporting - Catch points in FileLoaderUtils, SeriesScanUtil, and DeviceCollector wrap IOException/RuntimeException as CorruptedTsFileException - Normal queries: message mentions corruption, tells user to check logs (no file path) - read_tsfile queries: message includes full TsFile path - DiskChunkLoader/DiskAlignedChunkLoader expose getTsFile() for File reference - ErrorHandlingUtils handles CorruptedTsFileException with TSFILE_PROCESSOR_ERROR - AbstractDriverThread matches CorruptedTsFileException by instanceof - AbstractDriverThread catch CorruptedTsFileException as abort cause - i18n: add EXCEPTION_ constants for all five stages (en + zh) - IT: IoTDBQueryWithCorruptedTsFileIT tests metadata index and page data corruption --- .../IoTDBQueryWithCorruptedTsFileIT.java | 325 ++++++++++++++++++ .../iotdb/db/i18n/DataNodeQueryMessages.java | 26 ++ .../iotdb/db/i18n/DataNodeQueryMessages.java | 22 ++ .../exception/CorruptedTsFileException.java | 72 ++++ .../operator/source/FileLoaderUtils.java | 67 +++- .../operator/source/SeriesScanUtil.java | 43 ++- .../schedule/AbstractDriverThread.java | 7 + .../ExternalTsFileQueryResource.java | 106 ++++-- .../reader/chunk/DiskAlignedChunkLoader.java | 5 + .../read/reader/chunk/DiskChunkLoader.java | 5 + .../iotdb/db/utils/ErrorHandlingUtils.java | 3 + 11 files changed, 638 insertions(+), 43 deletions(-) create mode 100644 integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBQueryWithCorruptedTsFileIT.java create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/CorruptedTsFileException.java diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBQueryWithCorruptedTsFileIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBQueryWithCorruptedTsFileIT.java new file mode 100644 index 0000000000000..ae2fcb039930c --- /dev/null +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBQueryWithCorruptedTsFileIT.java @@ -0,0 +1,325 @@ +/* + * 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.relational.it.query.recent; + +import org.apache.iotdb.it.env.EnvFactory; +import org.apache.iotdb.it.framework.IoTDBTestRunner; +import org.apache.iotdb.itbase.category.TableLocalStandaloneIT; + +import org.apache.tsfile.common.conf.TSFileConfig; +import org.apache.tsfile.enums.ColumnCategory; +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.exception.write.WriteProcessException; +import org.apache.tsfile.file.metadata.TableSchema; +import org.apache.tsfile.read.TsFileSequenceReader; +import org.apache.tsfile.write.TsFileWriter; +import org.apache.tsfile.write.record.Tablet; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.apache.iotdb.db.it.utils.TestUtils.tableAssertTestFail; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(IoTDBTestRunner.class) +@Category({TableLocalStandaloneIT.class}) +public class IoTDBQueryWithCorruptedTsFileIT { + private static final String DATABASE_NAME = "test_corrupted_read_tsfile"; + + private static File tmpDir; + + @BeforeClass + public static void setUp() throws Exception { + EnvFactory.getEnv().initClusterEnvironment(); + try (Connection connection = EnvFactory.getEnv().getTableConnection(); + Statement statement = connection.createStatement()) { + statement.execute("CREATE DATABASE " + DATABASE_NAME); + } + } + + @Before + public void setUpBeforeTest() throws IOException { + tmpDir = new File(Files.createTempDirectory("corrupt-tsfile").toUri()); + } + + @After + public void tearDownAfterTest() { + deleteTmpDir(); + } + + @AfterClass + public static void tearDown() { + EnvFactory.getEnv().cleanClusterEnvironment(); + } + + @Test + public void testReadTsFileWithCorruptedMetadataIndexNode() throws Exception { + File tsFile = new File(tmpDir, "corrupt-meta.tsfile"); + try (TsFileWriter writer = new TsFileWriter(tsFile)) { + generateTable( + writer, "table1", Arrays.asList("tag1"), Arrays.asList("s1"), TSDataType.INT64, 1, 10); + } + + // TsFile layout: [Header] [Data] [MetadataIndex Tree] [TsFileMetadata] [Magic][Size] + // ↑ metaOffset ↑ fileMetadataPos + // + // MetadataIndexNode serialization: + // [entryCount (varInt)] [entry1]...[entryN] [endOffset (long, 8B)] [nodeType (1B)] + // nodeType valid values: 0=INTERNAL_DEVICE, 1=LEAF_DEVICE, 2=INTERNAL_MEASUREMENT, + // 3=LEAF_MEASUREMENT + // nodeType is the LAST byte before TsFileMetadata, i.e. at fileMetadataPos - 1 + long metaOffset; + long fileMetadataPos; + try (TsFileSequenceReader reader = new TsFileSequenceReader(tsFile.getAbsolutePath())) { + metaOffset = reader.readFileMetadata().getMetaOffset(); + fileMetadataPos = reader.getFileMetadataPos(); + } + + // Corrupt the nodeType byte to 0xFF (all valid types are 0-3) + byte[] fileBytes = Files.readAllBytes(tsFile.toPath()); + fileBytes[(int) fileMetadataPos - 1] = (byte) 0xFF; + Files.write(tsFile.toPath(), fileBytes); + + tableAssertTestFail( + "SELECT * FROM read_tsfile(PATHS => '" + toSqlPath(tsFile) + "')", + "timeseries metadata", + DATABASE_NAME); + } + + @Test + public void testReadTsFileWithCorruptedPageData() throws Exception { + File tsFile = new File(tmpDir, "corrupt-page.tsfile"); + try (TsFileWriter writer = new TsFileWriter(tsFile)) { + generateTable( + writer, "table1", Arrays.asList("tag1"), Arrays.asList("s1"), TSDataType.INT64, 1, 100); + } + + corruptDataSection(tsFile); + + tableAssertTestFail( + "SELECT * FROM read_tsfile(PATHS => '" + toSqlPath(tsFile) + "')", "TsFile", DATABASE_NAME); + } + + @Test + public void testNormalQueryWithCorruptedPageData() throws Exception { + String tableName = "corrupt_table"; + // 1. Create table and insert data via session — generates TsFiles in the data directory + try (Connection connection = EnvFactory.getEnv().getTableConnection(); + Statement statement = connection.createStatement()) { + statement.execute("USE " + DATABASE_NAME); + statement.execute( + "CREATE TABLE " + tableName + "(device_id STRING TAG, s1 INT64 FIELD, s2 INT64 FIELD)"); + for (int i = 1; i <= 200; i++) { + statement.execute( + "INSERT INTO " + + tableName + + "(time, device_id, s1, s2) VALUES(" + + i + + ", 'd" + + (i % 10) + + "', " + + i + + ", " + + (i * 10) + + ")"); + } + statement.execute("FLUSH"); + } + + // 2. Find the generated TsFile in the data directory + File sequenceDir = + new File( + EnvFactory.getEnv().getDataNodeWrapper(0).getDataNodeDir() + + File.separator + + "data" + + File.separator + + "sequence"); + File tsFile = findTsFileRecursively(sequenceDir); + if (tsFile == null) { + fail("Could not find TsFile in data directory: " + sequenceDir.getAbsolutePath()); + } + + // 3. Corrupt the data section of the TsFile + corruptDataSection(tsFile); + + // 4. Query — should fail with a corruption message that does NOT include the file path + try (Connection connection = EnvFactory.getEnv().getTableConnection(); + Statement statement = connection.createStatement()) { + statement.execute("USE " + DATABASE_NAME); + try { + statement.execute("SELECT * FROM " + tableName + " ORDER BY time"); + fail("Expected query on corrupted TsFile to fail"); + } catch (SQLException e) { + assertTrue( + "Error message should mention corruption without file path: " + e.getMessage(), + e.getMessage().contains("may be corrupted") + || e.getMessage().contains("check the logs")); + } + } + } + + /** + * Corrupts a block of bytes in the data section of a TsFile (between the header and the + * MetadataIndex tree). Uses {@link TsFileSequenceReader} to read {@code metaOffset} from + * TsFileMetadata, so corruption reliably hits compressed page data rather than metadata. + * + *

TsFile layout: [Header] [Data chunks] [MetadataIndex Tree] [TsFileMetadata] [Magic][Size] ↑ + * metaOffset + */ + private static void corruptDataSection(File tsFile) throws IOException { + long metaOffset; + try (TsFileSequenceReader reader = new TsFileSequenceReader(tsFile.getAbsolutePath())) { + metaOffset = reader.readFileMetadata().getMetaOffset(); + } + + byte[] fileBytes = Files.readAllBytes(tsFile.toPath()); + int magicLen = TSFileConfig.MAGIC_STRING.getBytes().length; + int dataStart = magicLen + Byte.BYTES; + int dataEnd = (int) metaOffset; + // Corrupt bytes in the middle of the data section — XOR 512 bytes to ensure decompression fails + int middle = dataStart + (dataEnd - dataStart) / 2; + int corruptLen = Math.min(512, dataEnd - middle); + for (int i = 0; i < corruptLen; i++) { + fileBytes[middle + i] ^= 0xFF; + } + Files.write(tsFile.toPath(), fileBytes); + } + + private static File findTsFileRecursively(File dir) { + if (dir == null || !dir.exists()) { + return null; + } + File[] files = dir.listFiles(); + if (files == null) { + return null; + } + for (File file : files) { + if (file.isDirectory()) { + File found = findTsFileRecursively(file); + if (found != null) { + return found; + } + } else if (file.getName().endsWith(".tsfile") && file.length() > 0) { + return file; + } + } + return null; + } + + private static void generateTable( + TsFileWriter writer, + String tableName, + List tagColumns, + List fieldColumns, + TSDataType fieldType, + long startTime, + long endTime) + throws IOException, WriteProcessException { + List columnNames = new ArrayList<>(tagColumns.size() + fieldColumns.size()); + List columnTypes = new ArrayList<>(tagColumns.size() + fieldColumns.size()); + List columnCategories = + new ArrayList<>(tagColumns.size() + fieldColumns.size()); + for (String tagColumn : tagColumns) { + columnNames.add(tagColumn); + columnTypes.add(TSDataType.STRING); + columnCategories.add(ColumnCategory.TAG); + } + for (String fieldColumn : fieldColumns) { + columnNames.add(fieldColumn); + columnTypes.add(fieldType); + columnCategories.add(ColumnCategory.FIELD); + } + + writer.registerTableSchema( + new TableSchema(tableName, columnNames, columnTypes, columnCategories)); + Tablet tablet = new Tablet(tableName, columnNames, columnTypes, columnCategories); + for (int deviceIndex = 1; deviceIndex <= 2; deviceIndex++) { + for (long time = startTime; time <= endTime; time++) { + int row = tablet.getRowSize(); + tablet.addTimestamp(row, time); + for (int i = 0; i < tagColumns.size(); i++) { + tablet.addValue(row, i, tagColumns.get(i) + "_" + deviceIndex); + } + for (int i = 0; i < fieldColumns.size(); i++) { + tablet.addValue(row, tagColumns.size() + i, time); + } + if (tablet.getRowSize() == tablet.getMaxRowNumber()) { + writer.writeTable(tablet); + tablet.reset(); + } + } + } + if (tablet.getRowSize() != 0) { + writer.writeTable(tablet); + } + } + + private static String toSqlPath(File file) { + return file.getAbsolutePath().replace("\\", "\\\\").replace("'", "''"); + } + + private static void deleteTmpDir() { + if (tmpDir == null || !tmpDir.exists()) { + return; + } + File[] files = tmpDir.listFiles(); + if (files != null) { + for (File file : files) { + deleteRecursively(file); + } + } + try { + Files.delete(tmpDir.toPath()); + } catch (IOException ignored) { + // ignore + } + } + + private static void deleteRecursively(File file) { + if (file.isDirectory()) { + File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + deleteRecursively(child); + } + } + } + try { + Files.delete(file.toPath()); + } catch (IOException ignored) { + // ignore + } + } +} diff --git a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java index 92242b62aa726..5839542d1b3d4 100644 --- a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java +++ b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java @@ -218,6 +218,32 @@ public final class DataNodeQueryMessages { "Error happened while scanning the file"; public static final String ALL_CACHED_CHUNKS_SHOULD_BE_CONSUMED_FIRST = "all cached chunks should be consumed first"; + public static final String + EXCEPTION_FAILED_TO_READ_TIMESERIES_METADATA_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_0B9E652E = + "Failed to read timeseries metadata. The TsFile may be corrupted," + + " please check the logs for the corrupted file path."; + public static final String EXCEPTION_FAILED_TO_READ_TIMESERIES_METADATA_FROM_TSFILE_ARG_B07568F8 = + "Failed to read timeseries metadata from TsFile: %s"; + public static final String + EXCEPTION_FAILED_TO_READ_CHUNK_DATA_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_F0FFE629 = + "Failed to read chunk data. The TsFile may be corrupted," + + " please check the logs for the corrupted file path."; + public static final String EXCEPTION_FAILED_TO_READ_CHUNK_DATA_FROM_TSFILE_ARG_B88F2496 = + "Failed to read chunk data from TsFile: %s"; + public static final String + EXCEPTION_FAILED_TO_DECODE_PAGE_DATA_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_54D7C6D9 = + "Failed to decode page data. The TsFile may be corrupted," + + " please check the logs for the corrupted file path."; + public static final String EXCEPTION_FAILED_TO_DECODE_PAGE_DATA_FROM_TSFILE_ARG_645F5377 = + "Failed to decode page data from TsFile: %s"; + public static final String + EXCEPTION_FAILED_TO_LOAD_PAGE_READER_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_05D35760 = + "Failed to load page reader. The TsFile may be corrupted," + + " please check the logs for the corrupted file path."; + public static final String EXCEPTION_FAILED_TO_LOAD_PAGE_READER_FROM_TSFILE_ARG_3B1CCC18 = + "Failed to load page reader from TsFile: %s"; + public static final String EXCEPTION_FAILED_TO_READ_METADATA_INDEX_NODE_FROM_TSFILE_ARG_EC5B6633 = + "Failed to read metadata index node from TsFile: %s"; public static final String OVERLAPPED_DATA_SHOULD_BE_CONSUMED_FIRST = "overlapped data should be consumed first"; public static final String NO_MORE_BATCH_DATA = diff --git a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java index 9ce80200a7b75..2ba8eb4162b84 100644 --- a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java +++ b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java @@ -205,6 +205,28 @@ public final class DataNodeQueryMessages { "扫描文件时发生错误"; public static final String ERROR_HAPPENED_WHILE_SCANNING_THE_FILE = "扫描文件时发生错误"; + public static final String + EXCEPTION_FAILED_TO_READ_TIMESERIES_METADATA_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_0B9E652E = + "读取时间序列元数据失败。TsFile 可能已损坏,请检查日志中的损坏文件路径。"; + public static final String EXCEPTION_FAILED_TO_READ_TIMESERIES_METADATA_FROM_TSFILE_ARG_B07568F8 = + "从 TsFile 读取时间序列元数据失败:%s"; + public static final String + EXCEPTION_FAILED_TO_READ_CHUNK_DATA_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_F0FFE629 = + "读取 chunk 数据失败。TsFile 可能已损坏,请检查日志中的损坏文件路径。"; + public static final String EXCEPTION_FAILED_TO_READ_CHUNK_DATA_FROM_TSFILE_ARG_B88F2496 = + "从 TsFile 读取 chunk 数据失败:%s"; + public static final String + EXCEPTION_FAILED_TO_DECODE_PAGE_DATA_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_54D7C6D9 = + "解码 page 数据失败。TsFile 可能已损坏,请检查日志中的损坏文件路径。"; + public static final String EXCEPTION_FAILED_TO_DECODE_PAGE_DATA_FROM_TSFILE_ARG_645F5377 = + "从 TsFile 解码 page 数据失败:%s"; + public static final String + EXCEPTION_FAILED_TO_LOAD_PAGE_READER_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_05D35760 = + "加载 page reader 失败。TsFile 可能已损坏,请检查日志中的损坏文件路径。"; + public static final String EXCEPTION_FAILED_TO_LOAD_PAGE_READER_FROM_TSFILE_ARG_3B1CCC18 = + "从 TsFile 加载 page reader 失败:%s"; + public static final String EXCEPTION_FAILED_TO_READ_METADATA_INDEX_NODE_FROM_TSFILE_ARG_EC5B6633 = + "从 TsFile 读取元数据索引节点失败:%s"; public static final String ALL_CACHED_CHUNKS_SHOULD_BE_CONSUMED_FIRST = "所有缓存的 chunk 应先被消费"; public static final String OVERLAPPED_DATA_SHOULD_BE_CONSUMED_FIRST = diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/CorruptedTsFileException.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/CorruptedTsFileException.java new file mode 100644 index 0000000000000..7a85307d8adeb --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/CorruptedTsFileException.java @@ -0,0 +1,72 @@ +/* + * 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.exception; + +import java.io.File; + +/** + * Thrown when a TsFile is detected to be corrupted during query execution. Extends RuntimeException + * so it bypasses all {@code catch (IOException)} blocks in the operator hierarchy and reaches the + * centralized error handling in ErrorHandlingUtils. + */ +public class CorruptedTsFileException extends RuntimeException { + + public enum Stage { + READ_TIMESERIES_METADATA, + READ_CHUNK_DATA, + LOAD_PAGE_READER, + DECODE_PAGE_DATA, + READ_METADATA_INDEX_NODE + } + + private final File tsFile; + private final Stage stage; + + /** + * Creates a CorruptedTsFileException. + * + *

The original exception {@code cause} is added via {@link #addSuppressed(Throwable)} rather + * than {@link #initCause(Throwable)}. This ensures that {@code + * ErrorHandlingCommonUtils.getRootCause(this)} returns this exception itself (not the wrapped + * IOException), so upstream error handling in {@code AbstractDriverThread} matches it correctly. + * The original cause stack trace is preserved via suppressed exceptions. + * + * @param tsFile the corrupted TsFile + * @param stage the operation that encountered the corruption + * @param message user-facing error message + * @param cause the original exception (preserved as suppressed) + */ + public CorruptedTsFileException(File tsFile, Stage stage, String message, Throwable cause) { + super(message); + this.tsFile = tsFile; + this.stage = stage; + if (cause != null) { + addSuppressed(cause); + } + } + + public File getTsFile() { + return tsFile; + } + + public Stage getStage() { + return stage; + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java index 2a0aa01f6d9a4..285017b4225d4 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java @@ -22,6 +22,7 @@ import org.apache.iotdb.commons.path.AlignedFullPath; import org.apache.iotdb.commons.path.NonAlignedFullPath; import org.apache.iotdb.db.exception.ChunkTypeInconsistentException; +import org.apache.iotdb.db.exception.CorruptedTsFileException; import org.apache.iotdb.db.i18n.DataNodeQueryMessages; import org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceContext; import org.apache.iotdb.db.queryengine.execution.fragment.QueryContext; @@ -54,6 +55,7 @@ import org.apache.tsfile.read.reader.IPageReader; import org.apache.tsfile.write.schema.IMeasurementSchema; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -163,6 +165,18 @@ public static TimeseriesMetadata loadTimeSeriesMetadata( } return timeSeriesMetadata; + } catch (Exception e) { + throw new CorruptedTsFileException( + resource.getTsFile(), + CorruptedTsFileException.Stage.READ_TIMESERIES_METADATA, + context.isExternalTsFileScan() + ? String.format( + DataNodeQueryMessages + .EXCEPTION_FAILED_TO_READ_TIMESERIES_METADATA_FROM_TSFILE_ARG_B07568F8, + resource.getTsFile()) + : DataNodeQueryMessages + .EXCEPTION_FAILED_TO_READ_TIMESERIES_METADATA_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_0B9E652E, + e); } finally { long costTime = System.nanoTime() - t1; if (loadFromMem) { @@ -250,6 +264,18 @@ public static AbstractAlignedTimeSeriesMetadata loadAlignedTimeSeriesMetadata( } } return alignedTimeSeriesMetadata; + } catch (Exception e) { + throw new CorruptedTsFileException( + resource.getTsFile(), + CorruptedTsFileException.Stage.READ_TIMESERIES_METADATA, + context.isExternalTsFileScan() + ? String.format( + DataNodeQueryMessages + .EXCEPTION_FAILED_TO_READ_TIMESERIES_METADATA_FROM_TSFILE_ARG_B07568F8, + resource.getTsFile()) + : DataNodeQueryMessages + .EXCEPTION_FAILED_TO_READ_TIMESERIES_METADATA_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_0B9E652E, + e); } finally { long costTime = System.nanoTime() - t1; if (loadFromMem) { @@ -469,25 +495,60 @@ public static List loadChunkMetadataList(ITimeSeriesMetadata tim * IOException will be thrown */ public static List loadPageReaderList( - IChunkMetadata chunkMetaData, Filter globalTimeFilter, List targetDataTypeList) + IChunkMetadata chunkMetaData, + Filter globalTimeFilter, + List targetDataTypeList, + FragmentInstanceContext context) throws IOException { checkArgument( chunkMetaData != null, DataNodeQueryMessages.EXCEPTION_CAN_QUOTE_T_INIT_NULL_CHUNKMETA_15C12BEE); IChunkLoader chunkLoader = chunkMetaData.getChunkLoader(); - IChunkReader chunkReader; + File tsFile = null; + if (chunkLoader instanceof DiskChunkLoader) { + tsFile = ((DiskChunkLoader) chunkLoader).getTsFile(); + } else if (chunkLoader instanceof DiskAlignedChunkLoader) { + tsFile = ((DiskAlignedChunkLoader) chunkLoader).getTsFile(); + } + final IChunkReader chunkReader; try { chunkReader = chunkLoader.getChunkReader(chunkMetaData, globalTimeFilter); } catch (ChunkTypeInconsistentException e) { // if the chunk in tsfile is a value chunk of aligned series but registered series is // non-aligned, we should skip all data of this chunk. return Collections.emptyList(); + } catch (Exception e) { + throw new CorruptedTsFileException( + tsFile, + CorruptedTsFileException.Stage.READ_CHUNK_DATA, + context.isExternalTsFileScan() + ? String.format( + DataNodeQueryMessages + .EXCEPTION_FAILED_TO_READ_CHUNK_DATA_FROM_TSFILE_ARG_B88F2496, + tsFile) + : DataNodeQueryMessages + .EXCEPTION_FAILED_TO_READ_CHUNK_DATA_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_F0FFE629, + e); } if (chunkMetaData.isDataTypeModifiedAndCannotUseStatistics()) { chunkReader.markDataTypeModifiedAndCannotUseStatistics(); } - return chunkReader.loadPageReaderList(); + try { + return chunkReader.loadPageReaderList(); + } catch (Exception e) { + throw new CorruptedTsFileException( + tsFile, + CorruptedTsFileException.Stage.LOAD_PAGE_READER, + context.isExternalTsFileScan() + ? String.format( + DataNodeQueryMessages + .EXCEPTION_FAILED_TO_LOAD_PAGE_READER_FROM_TSFILE_ARG_3B1CCC18, + tsFile) + : DataNodeQueryMessages + .EXCEPTION_FAILED_TO_LOAD_PAGE_READER_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_05D35760, + e); + } } /** diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/SeriesScanUtil.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/SeriesScanUtil.java index 52ddf505a5e54..1b8ca2b1020c1 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/SeriesScanUtil.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/SeriesScanUtil.java @@ -22,6 +22,7 @@ import org.apache.iotdb.calc.execution.filter.TopKRuntimeFilter; import org.apache.iotdb.commons.path.IFullPath; import org.apache.iotdb.commons.path.NonAlignedFullPath; +import org.apache.iotdb.db.exception.CorruptedTsFileException; import org.apache.iotdb.db.i18n.DataNodeQueryMessages; import org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceContext; import org.apache.iotdb.db.queryengine.execution.fragment.QueryContext; @@ -32,6 +33,8 @@ import org.apache.iotdb.db.storageengine.dataregion.memtable.AlignedReadOnlyMemChunk; import org.apache.iotdb.db.storageengine.dataregion.memtable.ReadOnlyMemChunk; import org.apache.iotdb.db.storageengine.dataregion.read.QueryDataSource; +import org.apache.iotdb.db.storageengine.dataregion.read.reader.chunk.DiskAlignedChunkLoader; +import org.apache.iotdb.db.storageengine.dataregion.read.reader.chunk.DiskChunkLoader; import org.apache.iotdb.db.storageengine.dataregion.read.reader.chunk.MemAlignedPageReader; import org.apache.iotdb.db.storageengine.dataregion.read.reader.chunk.MemChunkLoader; import org.apache.iotdb.db.storageengine.dataregion.read.reader.chunk.MemPageReader; @@ -79,6 +82,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.io.IOException; import java.io.Serializable; import java.nio.charset.StandardCharsets; @@ -695,6 +699,14 @@ private void unpackOneChunkMetaData(IChunkMetadata chunkMetaData) throws IOExcep long timestampInFileName = FileLoaderUtils.getTimestampInFileName(chunkMetaData); IChunkLoader chunkLoader = chunkMetaData.getChunkLoader(); + final File tsFile; + if (chunkLoader instanceof DiskChunkLoader) { + tsFile = ((DiskChunkLoader) chunkLoader).getTsFile(); + } else if (chunkLoader instanceof DiskAlignedChunkLoader) { + tsFile = ((DiskAlignedChunkLoader) chunkLoader).getTsFile(); + } else { + tsFile = null; + } if ((chunkLoader instanceof MemChunkLoader) && ((MemChunkLoader) chunkLoader).isStreamingQueryMemChunk()) { unpackOneFakeMemChunkMetaData( @@ -703,7 +715,7 @@ private void unpackOneChunkMetaData(IChunkMetadata chunkMetaData) throws IOExcep } List pageReaderList = FileLoaderUtils.loadPageReaderList( - chunkMetaData, scanOptions.getGlobalTimeFilter(), getTsDataTypeList()); + chunkMetaData, scanOptions.getGlobalTimeFilter(), getTsDataTypeList(), context); // init TsBlockBuilder for each page reader pageReaderList.forEach(p -> p.initTsBlockBuilder(getTsDataTypeList())); @@ -718,7 +730,8 @@ private void unpackOneChunkMetaData(IChunkMetadata chunkMetaData) throws IOExcep chunkMetaData.getVersion(), chunkMetaData.getOffsetOfChunkHeader(), iPageReader, - true)); + true, + tsFile)); } } else { for (int i = pageReaderList.size() - 1; i >= 0; i--) { @@ -729,7 +742,8 @@ private void unpackOneChunkMetaData(IChunkMetadata chunkMetaData) throws IOExcep chunkMetaData.getVersion(), chunkMetaData.getOffsetOfChunkHeader(), pageReaderList.get(i), - true)); + true, + tsFile)); } } } else { @@ -742,7 +756,8 @@ private void unpackOneChunkMetaData(IChunkMetadata chunkMetaData) throws IOExcep chunkMetaData.getVersion(), chunkMetaData.getOffsetOfChunkHeader(), pageReader, - false))); + false, + tsFile))); } if (LOGGER.isDebugEnabled()) { @@ -2083,6 +2098,7 @@ protected static class VersionPageReader implements IVersionPageReader { protected final boolean isSeq; protected final boolean isAligned; protected final boolean isMem; + protected final File tsFile; VersionPageReader( QueryContext context, @@ -2090,7 +2106,8 @@ protected static class VersionPageReader implements IVersionPageReader { long version, long offset, IPageReader data, - boolean isSeq) { + boolean isSeq, + File tsFile) { this.context = context; this.version = new MergeReaderPriority(fileTimestamp, version, offset, isSeq); this.data = data; @@ -2100,6 +2117,7 @@ protected static class VersionPageReader implements IVersionPageReader { || data instanceof MemAlignedPageReader || data instanceof TablePageReader; this.isMem = data instanceof MemPageReader || data instanceof MemAlignedPageReader; + this.tsFile = tsFile; } @SuppressWarnings("squid:S3740") @@ -2144,6 +2162,21 @@ public TsBlock getAllSatisfiedPageData(boolean ascending) throws IOException { CommonUtils.toString(tsBlock)); } return tsBlock; + } catch (Exception e) { + if (tsFile != null) { + throw new CorruptedTsFileException( + tsFile, + CorruptedTsFileException.Stage.DECODE_PAGE_DATA, + context.isExternalTsFileScan() + ? String.format( + DataNodeQueryMessages + .EXCEPTION_FAILED_TO_DECODE_PAGE_DATA_FROM_TSFILE_ARG_645F5377, + tsFile) + : DataNodeQueryMessages + .EXCEPTION_FAILED_TO_DECODE_PAGE_DATA_THE_TSFILE_MAY_BE_CORRUPTED_PLEASE_CHECK_THE_LOGS_FOR_THE_CORRUPTED_FILE_PATH_54D7C6D9, + e); + } + throw e; } finally { long time = System.nanoTime() - startTime; if (isAligned) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/schedule/AbstractDriverThread.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/schedule/AbstractDriverThread.java index 4fb9e8a8cb5ca..7735ecae3e8f7 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/schedule/AbstractDriverThread.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/schedule/AbstractDriverThread.java @@ -23,6 +23,7 @@ import org.apache.iotdb.commons.exception.IoTDBException; import org.apache.iotdb.commons.exception.IoTDBRuntimeException; import org.apache.iotdb.commons.utils.ErrorHandlingCommonUtils; +import org.apache.iotdb.db.exception.CorruptedTsFileException; import org.apache.iotdb.db.i18n.DataNodeQueryMessages; import org.apache.iotdb.db.queryengine.execution.schedule.task.DriverTask; import org.apache.iotdb.db.utils.SetThreadName; @@ -93,6 +94,12 @@ public void run() { next.setAbortCause( new IoTDBRuntimeException( rootCause.getMessage(), DATE_OUT_OF_RANGE.getStatusCode(), true)); + } else if (rootCause instanceof CorruptedTsFileException) { + // CorruptedTsFileException no longer chains the original IOException as its + // cause (it uses addSuppressed instead), so getRootCause returns the exception + // itself and we can match it here. + logger.warn(DataNodeQueryMessages.EXECUTEFAILED, rootCause); + next.setAbortCause(rootCause); } else { logger.warn(DataNodeQueryMessages.EXECUTEFAILED, rootCause); next.setAbortCause( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java index d16e602e3d23f..2f2b0ba5db24a 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java @@ -28,6 +28,7 @@ import org.apache.iotdb.commons.schema.filter.SchemaFilter; import org.apache.iotdb.commons.utils.FileUtils; import org.apache.iotdb.commons.utils.TestOnly; +import org.apache.iotdb.db.exception.CorruptedTsFileException; import org.apache.iotdb.db.i18n.DataNodeQueryMessages; import org.apache.iotdb.db.queryengine.common.MPPQueryContext; import org.apache.iotdb.db.queryengine.common.QueryId; @@ -161,6 +162,12 @@ public void collectDeviceEntries( } } sealDeviceTaskPartitions(); + } catch (CorruptedTsFileException e) { + // Device collection runs during the planning phase, outside any DriverTask thread. + // CorruptedTsFileException is a RuntimeException and would crash the DataNode if not + // caught here. Re-wrap with initCause so the planning/query infrastructure can surface + // the error message to the client. + throw new RuntimeException(e.getMessage(), e); } } @@ -671,24 +678,45 @@ private class DeviceCollector implements Closeable { private DeviceCollector() { try { for (int fileIndex = 0; fileIndex < tsFilePaths.size(); fileIndex++) { - TsFileSequenceReader reader = - FileReaderManager.getInstance() - .get(tsFilePaths.get(fileIndex), null, true, null, true); - deviceIteratorMap.put(fileIndex, new LazyTsFileDeviceIterator(reader, tableName, null)); + try { + TsFileSequenceReader reader = + FileReaderManager.getInstance() + .get(tsFilePaths.get(fileIndex), null, true, null, true); + deviceIteratorMap.put(fileIndex, new LazyTsFileDeviceIterator(reader, tableName, null)); + } catch (Exception e) { + throw corruptedMetadataIndexNodeException(fileIndex, e); + } } - } catch (IOException e) { + } catch (RuntimeException e) { close(); - throw new RuntimeException( - DataNodeQueryMessages.FAILED_TO_CREATE_EXTERNAL_TSFILE_DEVICE_COLLECTOR, e); + throw e; } } + private CorruptedTsFileException corruptedMetadataIndexNodeException( + int fileIndex, Exception cause) { + File tsFile = sharedTsFileResources.get(fileIndex).getTsFile(); + return new CorruptedTsFileException( + tsFile, + CorruptedTsFileException.Stage.READ_METADATA_INDEX_NODE, + String.format( + DataNodeQueryMessages + .EXCEPTION_FAILED_TO_READ_METADATA_INDEX_NODE_FROM_TSFILE_ARG_EC5B6633, + tsFile), + cause); + } + private boolean hasNextDevice() { - for (LazyTsFileDeviceIterator deviceIterator : deviceIteratorMap.values()) { - if (deviceIterator.hasNext() - || (deviceIterator.hasCurrent() - && !deviceIterator.getCurrentDeviceID().equals(currentDevice))) { - return true; + for (Map.Entry entry : deviceIteratorMap.entrySet()) { + try { + LazyTsFileDeviceIterator deviceIterator = entry.getValue(); + if (deviceIterator.hasNext() + || (deviceIterator.hasCurrent() + && !deviceIterator.getCurrentDeviceID().equals(currentDevice))) { + return true; + } + } catch (Exception e) { + throw corruptedMetadataIndexNodeException(entry.getKey(), e); } } return false; @@ -700,21 +728,25 @@ private IDeviceID nextDevice() { deviceIteratorMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); - LazyTsFileDeviceIterator deviceIterator = entry.getValue(); - IDeviceID currentFileDevice = null; - if (deviceIterator.hasCurrent()) { - currentFileDevice = deviceIterator.getCurrentDeviceID(); - } - if (currentFileDevice == null || currentFileDevice.equals(currentDevice)) { - if (deviceIterator.hasNext()) { - currentFileDevice = deviceIterator.next(); - } else { - iterator.remove(); - continue; + try { + LazyTsFileDeviceIterator deviceIterator = entry.getValue(); + IDeviceID currentFileDevice = null; + if (deviceIterator.hasCurrent()) { + currentFileDevice = deviceIterator.getCurrentDeviceID(); } - } - if (minDevice == null || minDevice.compareTo(currentFileDevice) > 0) { - minDevice = currentFileDevice; + if (currentFileDevice == null || currentFileDevice.equals(currentDevice)) { + if (deviceIterator.hasNext()) { + currentFileDevice = deviceIterator.next(); + } else { + iterator.remove(); + continue; + } + } + if (minDevice == null || minDevice.compareTo(currentFileDevice) > 0) { + minDevice = currentFileDevice; + } + } catch (Exception e) { + throw corruptedMetadataIndexNodeException(entry.getKey(), e); } } currentDevice = minDevice; @@ -725,15 +757,19 @@ private IDeviceID nextDevice() { private void collectCurrentDeviceOffsets() { List deviceOffsets = new ArrayList<>(); for (Map.Entry entry : deviceIteratorMap.entrySet()) { - LazyTsFileDeviceIterator deviceIterator = entry.getValue(); - if (currentDevice != null - && deviceIterator.hasCurrent() - && currentDevice.equals(deviceIterator.getCurrentDeviceID())) { - deviceOffsets.add( - new ExternalTsFileDeviceQueryTask.DeviceOffset( - entry.getKey(), - deviceIterator.getCurrentDeviceMeasurementNodeOffset()[0], - deviceIterator.getCurrentDeviceMeasurementNodeOffset()[1])); + try { + LazyTsFileDeviceIterator deviceIterator = entry.getValue(); + if (currentDevice != null + && deviceIterator.hasCurrent() + && currentDevice.equals(deviceIterator.getCurrentDeviceID())) { + deviceOffsets.add( + new ExternalTsFileDeviceQueryTask.DeviceOffset( + entry.getKey(), + deviceIterator.getCurrentDeviceMeasurementNodeOffset()[0], + deviceIterator.getCurrentDeviceMeasurementNodeOffset()[1])); + } + } catch (Exception e) { + throw corruptedMetadataIndexNodeException(entry.getKey(), e); } } currentDeviceOffsets = deviceOffsets; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/read/reader/chunk/DiskAlignedChunkLoader.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/read/reader/chunk/DiskAlignedChunkLoader.java index 7c1e9d1263518..5af552502857a 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/read/reader/chunk/DiskAlignedChunkLoader.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/read/reader/chunk/DiskAlignedChunkLoader.java @@ -37,6 +37,7 @@ import org.apache.tsfile.read.reader.chunk.AlignedChunkReader; import org.apache.tsfile.read.reader.chunk.TableChunkReader; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -142,4 +143,8 @@ public IChunkReader getChunkReader(IChunkMetadata chunkMetaData, Filter globalTi public TsFileID getTsFileID() { return resource.getTsFileID(); } + + public File getTsFile() { + return resource.getTsFile(); + } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/read/reader/chunk/DiskChunkLoader.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/read/reader/chunk/DiskChunkLoader.java index 7d259a9e50534..eb7abb7ed07f6 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/read/reader/chunk/DiskChunkLoader.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/read/reader/chunk/DiskChunkLoader.java @@ -37,6 +37,7 @@ import org.apache.tsfile.read.reader.IChunkReader; import org.apache.tsfile.read.reader.chunk.ChunkReader; +import java.io.File; import java.io.IOException; import static org.apache.iotdb.db.queryengine.metric.SeriesScanCostMetricSet.INIT_CHUNK_READER_NONALIGNED_DISK; @@ -122,4 +123,8 @@ public IChunkReader getChunkReader(IChunkMetadata chunkMetaData, Filter globalTi public TsFileID getTsFileID() { return resource.getTsFileID(); } + + public File getTsFile() { + return resource.getTsFile(); + } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java index 0a9216b8b288e..ada3aac4412a9 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java @@ -27,6 +27,7 @@ import org.apache.iotdb.commons.exception.SemanticException; import org.apache.iotdb.commons.utils.ErrorHandlingCommonUtils; import org.apache.iotdb.db.exception.BatchProcessException; +import org.apache.iotdb.db.exception.CorruptedTsFileException; import org.apache.iotdb.db.exception.QueryInBatchStatementException; import org.apache.iotdb.db.exception.StorageGroupNotReadyException; import org.apache.iotdb.db.exception.query.QueryTimeoutRuntimeException; @@ -186,6 +187,8 @@ private static TSStatus tryCatchQueryException(Exception e) { : RpcUtils.getStatus(((IoTDBException) t).getErrorCode(), rootCause.getMessage()); } else if (t instanceof TsFileRuntimeException) { return RpcUtils.getStatus(TSStatusCode.TSFILE_PROCESSOR_ERROR, rootCause.getMessage()); + } else if (t instanceof CorruptedTsFileException) { + return RpcUtils.getStatus(TSStatusCode.TSFILE_PROCESSOR_ERROR, t.getMessage()); } else if (t instanceof SemanticException) { if (t.getCause() instanceof IoTDBException) { return RpcUtils.getStatus( From 762a1606e864b0c9c603c4412e57e8ecdab9fa14 Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Fri, 24 Jul 2026 18:41:27 +0800 Subject: [PATCH 2/4] Remove unnecessary catch in collectDeviceEntries CorruptedTsFileException is a RuntimeException and the DataNode handles it fine without wrapping, so the catch-and-rewrap block is redundant. --- .../tvf/read_tsfile/ExternalTsFileQueryResource.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java index 2f2b0ba5db24a..9a96145bc8a0b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java @@ -162,12 +162,6 @@ public void collectDeviceEntries( } } sealDeviceTaskPartitions(); - } catch (CorruptedTsFileException e) { - // Device collection runs during the planning phase, outside any DriverTask thread. - // CorruptedTsFileException is a RuntimeException and would crash the DataNode if not - // caught here. Re-wrap with initCause so the planning/query infrastructure can surface - // the error message to the client. - throw new RuntimeException(e.getMessage(), e); } } From 0e5b97cb0a712aabb93acb48103770e4d7e1545c Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Fri, 24 Jul 2026 18:57:47 +0800 Subject: [PATCH 3/4] fix --- .../iotdb/db/exception/CorruptedTsFileException.java | 10 ++++++---- .../org/apache/iotdb/db/utils/ErrorHandlingUtils.java | 3 --- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/CorruptedTsFileException.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/CorruptedTsFileException.java index 7a85307d8adeb..6574b3a042dc8 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/CorruptedTsFileException.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/CorruptedTsFileException.java @@ -19,14 +19,16 @@ package org.apache.iotdb.db.exception; +import org.apache.tsfile.exception.TsFileRuntimeException; + import java.io.File; /** - * Thrown when a TsFile is detected to be corrupted during query execution. Extends RuntimeException - * so it bypasses all {@code catch (IOException)} blocks in the operator hierarchy and reaches the - * centralized error handling in ErrorHandlingUtils. + * Thrown when a TsFile is detected to be corrupted during query execution. Extends {@link + * TsFileRuntimeException} so it follows the existing TsFile error handling and bypasses all {@code + * catch (IOException)} blocks in the operator hierarchy. */ -public class CorruptedTsFileException extends RuntimeException { +public class CorruptedTsFileException extends TsFileRuntimeException { public enum Stage { READ_TIMESERIES_METADATA, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java index ada3aac4412a9..0a9216b8b288e 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java @@ -27,7 +27,6 @@ import org.apache.iotdb.commons.exception.SemanticException; import org.apache.iotdb.commons.utils.ErrorHandlingCommonUtils; import org.apache.iotdb.db.exception.BatchProcessException; -import org.apache.iotdb.db.exception.CorruptedTsFileException; import org.apache.iotdb.db.exception.QueryInBatchStatementException; import org.apache.iotdb.db.exception.StorageGroupNotReadyException; import org.apache.iotdb.db.exception.query.QueryTimeoutRuntimeException; @@ -187,8 +186,6 @@ private static TSStatus tryCatchQueryException(Exception e) { : RpcUtils.getStatus(((IoTDBException) t).getErrorCode(), rootCause.getMessage()); } else if (t instanceof TsFileRuntimeException) { return RpcUtils.getStatus(TSStatusCode.TSFILE_PROCESSOR_ERROR, rootCause.getMessage()); - } else if (t instanceof CorruptedTsFileException) { - return RpcUtils.getStatus(TSStatusCode.TSFILE_PROCESSOR_ERROR, t.getMessage()); } else if (t instanceof SemanticException) { if (t.getCause() instanceof IoTDBException) { return RpcUtils.getStatus( From a41de955ccd688729d8ff7e3c11a29d7d4332a3c Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Fri, 24 Jul 2026 19:01:36 +0800 Subject: [PATCH 4/4] fix bug --- .../execution/operator/source/FileLoaderUtils.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java index 285017b4225d4..810d9f54c62df 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java @@ -519,6 +519,9 @@ public static List loadPageReaderList( // non-aligned, we should skip all data of this chunk. return Collections.emptyList(); } catch (Exception e) { + if (tsFile == null) { + throw e; + } throw new CorruptedTsFileException( tsFile, CorruptedTsFileException.Stage.READ_CHUNK_DATA, @@ -537,6 +540,9 @@ public static List loadPageReaderList( try { return chunkReader.loadPageReaderList(); } catch (Exception e) { + if (tsFile == null) { + throw e; + } throw new CorruptedTsFileException( tsFile, CorruptedTsFileException.Stage.LOAD_PAGE_READER,