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

ColinLeeo pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/tsfile.git


The following commit(s) were added to refs/heads/develop by this push:
     new 46cf0a126 Fix remaining C++ memory leak paths (#881)
46cf0a126 is described below

commit 46cf0a126548e7d9292fa5953a0adb53a7eeff26
Author: Colin Lee <[email protected]>
AuthorDate: Thu Jul 30 09:44:30 2026 +0800

    Fix remaining C++ memory leak paths (#881)
    
    * fix(cpp): close remaining memory leak paths
    
    * test(cpp): make reader leak tests MSVC-safe
---
 cpp/CMakeLists.txt                             |   3 +
 cpp/src/common/allocator/alloc_base.h          |   3 +
 cpp/src/common/allocator/mem_alloc.cc          |  18 +++-
 cpp/src/common/container/array.h               |  10 ++-
 cpp/src/common/container/byte_buffer.h         |  23 +++--
 cpp/src/common/container/sorted_array.h        |  10 ++-
 cpp/src/common/tablet.cc                       |  19 ++--
 cpp/src/common/tablet.h                        |  20 ++++-
 cpp/src/compress/lzo_compressor.cc             |  82 +++++++++--------
 cpp/src/compress/snappy_compressor.cc          |  41 ++++-----
 cpp/src/reader/aligned_chunk_reader.cc         | 119 ++++++++++++++-----------
 cpp/src/reader/chunk_reader.cc                 |   9 +-
 cpp/test/common/container/array_test.cc        |  14 ++-
 cpp/test/common/container/byte_buffer_test.cc  |  19 +++-
 cpp/test/common/container/sorted_array_test.cc |  13 +++
 cpp/test/common/tablet_test.cc                 |  25 +++++-
 cpp/test/compress/lzo_compressor_test.cc       |  68 ++++++++++++++
 cpp/test/compress/snappy_compressor_test.cc    |  20 +++++
 cpp/test/reader/chunk_reader_resource_test.cc  | 117 ++++++++++++++++++++++++
 19 files changed, 492 insertions(+), 141 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 5b5fe5249..efdc5ecfb 100755
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -211,6 +211,9 @@ endif ()
 
 option(BUILD_TEST "Build tests" ON)
 message("cmake using: BUILD_TEST=${BUILD_TEST}")
+if (BUILD_TEST)
+    add_definitions(-DENABLE_TEST)
+endif ()
 
 option(BUILD_TOOLS "Build the tsfile command-line tools" ON)
 message("cmake using: BUILD_TOOLS=${BUILD_TOOLS}")
diff --git a/cpp/src/common/allocator/alloc_base.h 
b/cpp/src/common/allocator/alloc_base.h
index dd2e0ab61..2b97c8f83 100644
--- a/cpp/src/common/allocator/alloc_base.h
+++ b/cpp/src/common/allocator/alloc_base.h
@@ -65,6 +65,9 @@ extern TSFILE_API const char* g_mod_names[__LAST_MOD_ID];
 void* mem_alloc(uint32_t size, AllocModID mid);
 void mem_free(void* ptr);
 void* mem_realloc(void* ptr, uint32_t size);
+#ifdef ENABLE_TEST
+TSFILE_API void TEST_fail_next_mem_realloc();
+#endif
 
 class ModStat {
    public:
diff --git a/cpp/src/common/allocator/mem_alloc.cc 
b/cpp/src/common/allocator/mem_alloc.cc
index b7c5c09c1..deda0b8d8 100644
--- a/cpp/src/common/allocator/mem_alloc.cc
+++ b/cpp/src/common/allocator/mem_alloc.cc
@@ -22,6 +22,7 @@
 #endif
 #include <string.h>
 
+#include <atomic>
 #include <iomanip>
 #include <iostream>
 
@@ -33,6 +34,16 @@
 
 namespace common {
 
+#ifdef ENABLE_TEST
+namespace {
+std::atomic<bool> g_fail_next_mem_realloc(false);
+}
+
+void TEST_fail_next_mem_realloc() {
+    g_fail_next_mem_realloc.store(true, std::memory_order_release);
+}
+#endif
+
 const char* g_mod_names[__LAST_MOD_ID] = {
     /*  0 */ "DEFAULT",
     /*  1 */ "TVLIST_DATA",
@@ -139,6 +150,11 @@ void mem_free(void* ptr) {
 }
 
 void* mem_realloc(void* ptr, uint32_t size) {
+#ifdef ENABLE_TEST
+    if (g_fail_next_mem_realloc.exchange(false, std::memory_order_acq_rel)) {
+        return nullptr;
+    }
+#endif
     char* p = static_cast<char*>(ptr);
     char* raw_ptr = p - ALIGNMENT;
     const uint64_t header =
@@ -222,4 +238,4 @@ void ModStat::print_stat() {
 
 BaseAllocator g_base_allocator;
 
-}  // end namespace common
\ No newline at end of file
+}  // end namespace common
diff --git a/cpp/src/common/container/array.h b/cpp/src/common/container/array.h
index 7a055808f..9bb05d4ad 100644
--- a/cpp/src/common/container/array.h
+++ b/cpp/src/common/container/array.h
@@ -136,12 +136,13 @@ class Array {
         }
 
         size_t new_capacity = (size_t)tmp;
-        array_ =
+        ValueType* new_array =
             (ValueType*)mem_realloc(array_, new_capacity * sizeof(*(array_)));
-        if (UNLIKELY(nullptr == array_)) {
+        if (UNLIKELY(nullptr == new_array)) {
             // log_err("realloc failed.");
             return E_OOM;
         }
+        array_ = new_array;
         capacity_ = new_capacity;
         return E_OK;
     }
@@ -150,12 +151,13 @@ class Array {
         size_t new_capacity = (size_t)(capacity_ / 2);
         // if the size passed to realloc is smaller than before, OS will
         // automatically release the rest memory
-        array_ =
+        ValueType* new_array =
             (ValueType*)mem_realloc(array_, new_capacity * sizeof(*(array_)));
-        if (UNLIKELY(nullptr == array_)) {
+        if (UNLIKELY(nullptr == new_array)) {
             // log_err("malloc failed.");
             return E_OOM;
         }
+        array_ = new_array;
         capacity_ = new_capacity;
         return E_OK;
     }
diff --git a/cpp/src/common/container/byte_buffer.h 
b/cpp/src/common/container/byte_buffer.h
index 4e2dfab15..0f313acdc 100644
--- a/cpp/src/common/container/byte_buffer.h
+++ b/cpp/src/common/container/byte_buffer.h
@@ -52,13 +52,18 @@ class ByteBuffer {
 
     FORCE_INLINE void reset() { real_data_size_ = 0; }
 
-    FORCE_INLINE void extend_memory(uint32_t new_size) {
+    FORCE_INLINE int extend_memory(uint32_t new_size) {
         ASSERT(new_size > reserved_size_);
-        data_ = static_cast<char*>(mem_realloc(data_, new_size));
+        char* new_data = static_cast<char*>(mem_realloc(data_, new_size));
+        if (UNLIKELY(new_data == nullptr)) {
+            return E_OOM;
+        }
+        data_ = new_data;
         reserved_size_ = new_size;
+        return E_OK;
     }
 
-    FORCE_INLINE void append_variable_value(const char* value, uint32_t len) {
+    FORCE_INLINE int append_variable_value(const char* value, uint32_t len) {
         // dynamic growth
         if (UNLIKELY((real_data_size_ + len + variable_type_len_) >
                      reserved_size_)) {
@@ -67,7 +72,9 @@ class ByteBuffer {
                 g_config_value_.tsblock_mem_inc_step_size_ > len
                     ? g_config_value_.tsblock_mem_inc_step_size_
                     : (len + 1);
-            extend_memory(reserved_size_ + growth_size);
+            if (UNLIKELY(extend_memory(reserved_size_ + growth_size) != E_OK)) 
{
+                return E_OOM;
+            }
         }
 
         ASSERT(data_);
@@ -80,9 +87,10 @@ class ByteBuffer {
             memcpy(&data_[real_data_size_], value, len);
             real_data_size_ += len;
         }
+        return E_OK;
     }
 
-    FORCE_INLINE void append_fixed_value(const char* value, uint32_t len) {
+    FORCE_INLINE int append_fixed_value(const char* value, uint32_t len) {
         // dynamic growth
         if (UNLIKELY(real_data_size_ + len > reserved_size_)) {
             // extreme scenarios, when encountering very long string
@@ -90,12 +98,15 @@ class ByteBuffer {
                 g_config_value_.tsblock_mem_inc_step_size_ > len
                     ? g_config_value_.tsblock_mem_inc_step_size_
                     : (len + 1);
-            extend_memory(reserved_size_ + growth_size);
+            if (UNLIKELY(extend_memory(reserved_size_ + growth_size) != E_OK)) 
{
+                return E_OOM;
+            }
         }
 
         ASSERT(data_);
         memcpy(&data_[real_data_size_], value, len);
         real_data_size_ += len;
+        return E_OK;
     }
 
     // for fixed len value
diff --git a/cpp/src/common/container/sorted_array.h 
b/cpp/src/common/container/sorted_array.h
index 0713449dd..3e3b29289 100644
--- a/cpp/src/common/container/sorted_array.h
+++ b/cpp/src/common/container/sorted_array.h
@@ -139,12 +139,13 @@ class SortedArray {
         }
 
         size_t new_capacity = (size_t)tmp;
-        array_ =
+        ValueType* new_array =
             (ValueType*)mem_realloc(array_, new_capacity * sizeof(*(array_)));
-        if (UNLIKELY(nullptr == array_)) {
+        if (UNLIKELY(nullptr == new_array)) {
             // log_err("realloc failed.");
             return E_OOM;
         }
+        array_ = new_array;
         capacity_ = new_capacity;
         return E_OK;
     }
@@ -153,14 +154,15 @@ class SortedArray {
         size_t new_capacity = (size_t)(capacity_ / 2);
         // if the size passed to realloc is smaller than before, OS will
         // automatically release the rest memory
-        array_ = (ValueType*)mem_realloc(
+        ValueType* new_array = (ValueType*)mem_realloc(
             array_,
             new_capacity *
                 sizeof(*(array_)));  // TODO: user ourself's mem_alloc()
-        if (UNLIKELY(nullptr == array_)) {
+        if (UNLIKELY(nullptr == new_array)) {
             // log_err("malloc failed.");
             return E_OOM;
         }
+        array_ = new_array;
         capacity_ = new_capacity;
         return E_OK;
     }
diff --git a/cpp/src/common/tablet.cc b/cpp/src/common/tablet.cc
index 7c8fec561..3bfc54bb9 100644
--- a/cpp/src/common/tablet.cc
+++ b/cpp/src/common/tablet.cc
@@ -423,15 +423,19 @@ void* Tablet::get_value(int row_index, uint32_t 
schema_index,
 }
 
 template <>
-void Tablet::process_val(uint32_t row_index, uint32_t schema_index,
-                         common::String str) {
-    value_matrix_[schema_index].string_col->append(row_index, str.buf_,
-                                                   str.len_);
+int Tablet::process_val(uint32_t row_index, uint32_t schema_index,
+                        common::String str) {
+    int ret = value_matrix_[schema_index].string_col->append(
+        row_index, str.buf_, str.len_);
+    if (ret != E_OK) {
+        return ret;
+    }
     bitmaps_[schema_index].clear(row_index); /* mark as non-null */
+    return E_OK;
 }
 
 template <typename T>
-void Tablet::process_val(uint32_t row_index, uint32_t schema_index, T val) {
+int Tablet::process_val(uint32_t row_index, uint32_t schema_index, T val) {
     switch (schema_vec_->at(schema_index).data_type_) {
         case common::BOOLEAN:
             (value_matrix_[schema_index].bool_data)[row_index] =
@@ -459,6 +463,7 @@ void Tablet::process_val(uint32_t row_index, uint32_t 
schema_index, T val) {
             ASSERT(false);
     }
     bitmaps_[schema_index].clear(row_index); /* mark as non-null */
+    return E_OK;
 }
 
 template <typename T>
@@ -475,7 +480,7 @@ int Tablet::add_value(uint32_t row_index, uint32_t 
schema_index, T val) {
         if (UNLIKELY(!TypeMatch<T>(schema.data_type_))) {
             return E_TYPE_NOT_MATCH;
         }
-        process_val(row_index, schema_index, val);
+        ret = process_val(row_index, schema_index, val);
     }
     return ret;
 }
@@ -492,7 +497,7 @@ int Tablet::add_value(uint32_t row_index, uint32_t 
schema_index, std::tm val) {
     }
     int32_t date_int;
     if (RET_SUCC(common::DateConverter::date_to_int(val, date_int))) {
-        process_val(row_index, schema_index, date_int);
+        ret = process_val(row_index, schema_index, date_int);
     }
     return ret;
 }
diff --git a/cpp/src/common/tablet.h b/cpp/src/common/tablet.h
index c34408494..095157ac7 100644
--- a/cpp/src/common/tablet.h
+++ b/cpp/src/common/tablet.h
@@ -80,16 +80,28 @@ class Tablet {
             if (offsets) offsets[0] = 0;
         }
 
-        void append(uint32_t row, const char* data, uint32_t len) {
+        int append(uint32_t row, const char* data, uint32_t len) {
             // Grow buffer if needed
             if (buf_used + len > buf_capacity) {
-                buf_capacity = buf_capacity * 2 + len;
-                buffer = (char*)common::mem_realloc(buffer, buf_capacity);
+                uint64_t new_capacity_64 =
+                    static_cast<uint64_t>(buf_capacity) * 2 + len;
+                if (UNLIKELY(new_capacity_64 > UINT32_MAX)) {
+                    return common::E_OVERFLOW;
+                }
+                uint32_t new_capacity = static_cast<uint32_t>(new_capacity_64);
+                char* new_buffer =
+                    (char*)common::mem_realloc(buffer, new_capacity);
+                if (UNLIKELY(new_buffer == nullptr)) {
+                    return common::E_OOM;
+                }
+                buffer = new_buffer;
+                buf_capacity = new_capacity;
             }
             memcpy(buffer + buf_used, data, len);
             offsets[row] = static_cast<int32_t>(buf_used);
             offsets[row + 1] = static_cast<int32_t>(buf_used + len);
             buf_used += len;
+            return common::E_OK;
         }
 
         const char* get_str(uint32_t row) const {
@@ -393,7 +405,7 @@ class Tablet {
 
    private:
     template <typename T>
-    void process_val(uint32_t row_index, uint32_t schema_index, T val);
+    int process_val(uint32_t row_index, uint32_t schema_index, T val);
     uint32_t max_row_num_;
     uint32_t cur_row_size_;
     std::string insert_target_name_;
diff --git a/cpp/src/compress/lzo_compressor.cc 
b/cpp/src/compress/lzo_compressor.cc
index 0400b039c..8cf049428 100644
--- a/cpp/src/compress/lzo_compressor.cc
+++ b/cpp/src/compress/lzo_compressor.cc
@@ -45,46 +45,53 @@ int LZOCompressor::compress(char* uncompressed_buf,
                             uint32_t uncompressed_buf_len,
                             char*& compressed_buf,
                             uint32_t& compressed_buf_len) {
-    int ret = E_OK;
+    compressed_buf = nullptr;
+    compressed_buf_len = 0;
     size_t max_dst_size = lzokay::compress_worst_size(uncompressed_buf_len);
-    compressed_buf = (char*)mem_alloc(max_dst_size, MOD_COMPRESSOR_OBJ);
-    if (compressed_buf == nullptr) {
-        ret = E_OOM;
-    } else {
-        size_t compressed_len = 0;
-        uint8_t* srcUint8 = reinterpret_cast<uint8_t*>(uncompressed_buf);
-        uint8_t* dstUint8 = reinterpret_cast<uint8_t*>(compressed_buf);
-        lzokay::EResult compress_result =
-            lzokay::compress(srcUint8, uncompressed_buf_len, dstUint8,
-                             max_dst_size, compressed_len);
-        if (compress_result == lzokay::EResult::Success) {
-            char* compress_data = (char*)mem_realloc(dstUint8, compressed_len);
-            if (compress_data == nullptr) {
-                ret = E_OOM;
-            } else {
-                compressed_buf = compress_data;
-                compressed_buf_ = compress_data;
-                compressed_buf_len = compressed_len;
-            }
-        } else {
-            ret = E_COMPRESS_ERR;
-        }
+    char* allocated_buf = (char*)mem_alloc(max_dst_size, MOD_COMPRESSOR_OBJ);
+    if (allocated_buf == nullptr) {
+        return E_OOM;
     }
-    return ret;
+
+    size_t compressed_len = 0;
+    lzokay::EResult compress_result = lzokay::compress(
+        reinterpret_cast<uint8_t*>(uncompressed_buf), uncompressed_buf_len,
+        reinterpret_cast<uint8_t*>(allocated_buf), max_dst_size,
+        compressed_len);
+    if (compress_result != lzokay::EResult::Success) {
+        mem_free(allocated_buf);
+        return E_COMPRESS_ERR;
+    }
+
+    char* resized_buf = (char*)mem_realloc(allocated_buf, compressed_len);
+    if (resized_buf == nullptr) {
+        mem_free(allocated_buf);
+        return E_OOM;
+    }
+
+    compressed_buf = resized_buf;
+    compressed_buf_ = resized_buf;
+    compressed_buf_len = compressed_len;
+    return E_OK;
 }
 
 void LZOCompressor::after_compress(char* compressed_buf) {
     if (compressed_buf != nullptr) {
         mem_free(compressed_buf);
+        if (compressed_buf_ == compressed_buf) {
+            compressed_buf_ = nullptr;
+        }
     }
 }
 
 int LZOCompressor::uncompress(char* compressed_buf, uint32_t 
compressed_buf_len,
                               char*& uncompressed_buf,
                               uint32_t& uncompressed_buf_len) {
-    int ret = E_OK;
+    uncompressed_buf = nullptr;
+    uncompressed_buf_len = 0;
+    int ret = E_COMPRESS_ERR;
     char* regen_buffer = nullptr;
-    size_t ulength;
+    size_t ulength = 0;
     constexpr float ratio[] = {1.5, 2.5, 3.5, 4.5, 255};
     for (uint8_t i = 0; i < UNCOMPRESSED_TIME; ++i) {
         regen_buffer =
@@ -101,16 +108,16 @@ int LZOCompressor::uncompress(char* compressed_buf, 
uint32_t compressed_buf_len,
                 regen_buffer = nullptr;
                 ret = E_COMPRESS_ERR;
             } else {
-                char* compress_data = (char*)mem_realloc(regen_buffer, 
ulength);
-                if (regen_buffer == nullptr) {
-                    ret = E_OOM;
-                } else {
-                    ret = E_OK;
-                    uncompressed_buf_len = ulength;
-                    uncompressed_buf_ = compress_data;
-                    uncompressed_buf = compress_data;
-                    break;
+                char* resized_buf = (char*)mem_realloc(regen_buffer, ulength);
+                if (resized_buf == nullptr) {
+                    mem_free(regen_buffer);
+                    return E_OOM;
                 }
+                ret = E_OK;
+                uncompressed_buf_len = ulength;
+                uncompressed_buf_ = resized_buf;
+                uncompressed_buf = resized_buf;
+                break;
             }
         }
     }
@@ -120,7 +127,10 @@ int LZOCompressor::uncompress(char* compressed_buf, 
uint32_t compressed_buf_len,
 void LZOCompressor::after_uncompress(char* uncompressed_buf) {
     if (uncompressed_buf != nullptr) {
         mem_free(uncompressed_buf);
+        if (uncompressed_buf_ == uncompressed_buf) {
+            uncompressed_buf_ = nullptr;
+        }
     }
 }
 
-}  // end namespace storage
\ No newline at end of file
+}  // end namespace storage
diff --git a/cpp/src/compress/snappy_compressor.cc 
b/cpp/src/compress/snappy_compressor.cc
index e78a67ac3..c825f2232 100644
--- a/cpp/src/compress/snappy_compressor.cc
+++ b/cpp/src/compress/snappy_compressor.cc
@@ -45,31 +45,28 @@ int SnappyCompressor::compress(char* uncompressed_buf,
                                uint32_t uncompressed_buf_len,
                                char*& compressed_buf,
                                uint32_t& compressed_buf_len) {
-    int ret = E_OK;
+    compressed_buf = nullptr;
+    compressed_buf_len = 0;
     size_t max_dst_size = snappy::MaxCompressedLength(uncompressed_buf_len);
-    compressed_buf = (char*)mem_alloc(max_dst_size, MOD_COMPRESSOR_OBJ);
-    if (compressed_buf == nullptr) {
-        ret = E_OOM;
-    } else {
-        size_t compressed_len = 0;
-        snappy::RawCompress(uncompressed_buf, uncompressed_buf_len,
-                            compressed_buf, &compressed_len);
-        if (compressed_buf == nullptr) {
-            ret = E_COMPRESS_ERR;
-        } else {
-            char* compressed_data = (char*)mem_realloc(
-                compressed_buf, static_cast<uint32_t>(compressed_len));
-            if (compressed_data == nullptr) {
-                ret = E_OOM;
-            } else {
-                compressed_buf = compressed_data;
-                compressed_buf_ = compressed_data;
-                compressed_buf_len = compressed_len;
-            }
-        }
+    char* allocated_buf = (char*)mem_alloc(max_dst_size, MOD_COMPRESSOR_OBJ);
+    if (allocated_buf == nullptr) {
+        return E_OOM;
     }
 
-    return ret;
+    size_t compressed_len = 0;
+    snappy::RawCompress(uncompressed_buf, uncompressed_buf_len, allocated_buf,
+                        &compressed_len);
+    char* compressed_data = (char*)mem_realloc(
+        allocated_buf, static_cast<uint32_t>(compressed_len));
+    if (compressed_data == nullptr) {
+        mem_free(allocated_buf);
+        return E_OOM;
+    }
+
+    compressed_buf = compressed_data;
+    compressed_buf_ = compressed_data;
+    compressed_buf_len = compressed_len;
+    return E_OK;
 }
 
 void SnappyCompressor::after_compress(char* compressed_buf) {
diff --git a/cpp/src/reader/aligned_chunk_reader.cc 
b/cpp/src/reader/aligned_chunk_reader.cc
index 4b89e659b..795ec6b80 100644
--- a/cpp/src/reader/aligned_chunk_reader.cc
+++ b/cpp/src/reader/aligned_chunk_reader.cc
@@ -87,6 +87,12 @@ void AlignedChunkReader::reset() {
         time_compressor_->after_uncompress(time_uncompressed_buf_);
         time_uncompressed_buf_ = nullptr;
     }
+    if (value_uncompressed_buf_ != nullptr && value_compressor_ != nullptr) {
+        value_compressor_->after_uncompress(value_uncompressed_buf_);
+        value_uncompressed_buf_ = nullptr;
+    }
+    time_in_.reset();
+    value_in_.reset();
 
     // Multi-value reset
     for (auto* col : value_columns_) {
@@ -250,20 +256,23 @@ int AlignedChunkReader::load_by_aligned_meta(ChunkMeta* 
time_chunk_meta,
     ret = read_file_->read(time_chunk_meta_->offset_of_chunk_header_,
                            time_file_data_buf, file_data_time_buf_size_,
                            ret_read_len);
-    if (IS_SUCC(ret) && ret_read_len < ChunkHeader::MIN_SERIALIZED_SIZE) {
+    if (!IS_SUCC(ret)) {
+        mem_free(time_file_data_buf);
+        return ret;
+    }
+    if (ret_read_len < ChunkHeader::MIN_SERIALIZED_SIZE) {
         ret = E_TSFILE_CORRUPTED;
         LOGE("file corrupted, ret=" << ret << ", offset="
                                     << 
time_chunk_meta_->offset_of_chunk_header_
                                     << "read_len=" << ret_read_len);
         mem_free(time_file_data_buf);
+        return ret;
     }
-    if (IS_SUCC(ret)) {
-        time_in_stream_.wrap_from(time_file_data_buf, ret_read_len);
-        if (RET_FAIL(time_chunk_header_.deserialize_from(time_in_stream_))) {
-        } else {
-            time_chunk_visit_offset_ = time_in_stream_.read_pos();
-        }
+    time_in_stream_.wrap_from(time_file_data_buf, ret_read_len);
+    if (RET_FAIL(time_chunk_header_.deserialize_from(time_in_stream_))) {
+        return ret;
     }
+    time_chunk_visit_offset_ = time_in_stream_.read_pos();
     /* ================ deserialize value_chunk_header ================*/
     ret_read_len = 0;
     char* value_file_data_buf =
@@ -274,35 +283,38 @@ int AlignedChunkReader::load_by_aligned_meta(ChunkMeta* 
time_chunk_meta,
     ret = read_file_->read(value_chunk_meta_->offset_of_chunk_header_,
                            value_file_data_buf, file_data_value_buf_size_,
                            ret_read_len);
-    if (IS_SUCC(ret) && ret_read_len < ChunkHeader::MIN_SERIALIZED_SIZE) {
+    if (!IS_SUCC(ret)) {
+        mem_free(value_file_data_buf);
+        return ret;
+    }
+    if (ret_read_len < ChunkHeader::MIN_SERIALIZED_SIZE) {
         ret = E_TSFILE_CORRUPTED;
         LOGE("file corrupted, ret="
              << ret << ", offset=" << 
value_chunk_meta_->offset_of_chunk_header_
              << "read_len=" << ret_read_len);
         mem_free(value_file_data_buf);
+        return ret;
     }
-    if (IS_SUCC(ret)) {
-        value_in_stream_.wrap_from(value_file_data_buf, ret_read_len);
-        if (RET_FAIL(value_chunk_header_.deserialize_from(value_in_stream_))) {
-        } else if (RET_FAIL(alloc_compressor_and_decoder(
-                       time_decoder_, time_compressor_,
-                       time_chunk_header_.encoding_type_,
-                       time_chunk_header_.data_type_,
-                       time_chunk_header_.compression_type_))) {
-        } else if (RET_FAIL(alloc_compressor_and_decoder(
-                       value_decoder_, value_compressor_,
-                       value_chunk_header_.encoding_type_,
-                       value_chunk_header_.data_type_,
-                       value_chunk_header_.compression_type_))) {
-        } else {
-            value_chunk_visit_offset_ = value_in_stream_.read_pos();
+    value_in_stream_.wrap_from(value_file_data_buf, ret_read_len);
+    if (RET_FAIL(value_chunk_header_.deserialize_from(value_in_stream_))) {
+    } else if (RET_FAIL(alloc_compressor_and_decoder(
+                   time_decoder_, time_compressor_,
+                   time_chunk_header_.encoding_type_,
+                   time_chunk_header_.data_type_,
+                   time_chunk_header_.compression_type_))) {
+    } else if (RET_FAIL(alloc_compressor_and_decoder(
+                   value_decoder_, value_compressor_,
+                   value_chunk_header_.encoding_type_,
+                   value_chunk_header_.data_type_,
+                   value_chunk_header_.compression_type_))) {
+    } else {
+        value_chunk_visit_offset_ = value_in_stream_.read_pos();
 #if DEBUG_SE
-            std::cout << "AlignedChunkReader::load_by_meta, time_chunk_header="
-                      << time_chunk_header_
-                      << ", value_chunk_header=" << value_chunk_header_
-                      << std::endl;
+        std::cout << "AlignedChunkReader::load_by_meta, time_chunk_header="
+                  << time_chunk_header_
+                  << ", value_chunk_header=" << value_chunk_header_
+                  << std::endl;
 #endif
-        }
     }
     return ret;
 }
@@ -445,11 +457,11 @@ int AlignedChunkReader::read_from_file_and_rewrap(
         (want_size < DEFAULT_READ_SIZE ? DEFAULT_READ_SIZE : want_size);
     if (file_data_buf_size < read_size ||
         (may_shrink && read_size < file_data_buf_size / 10)) {
-        file_data_buf = (char*)mem_realloc(file_data_buf, read_size);
-        if (IS_NULL(file_data_buf)) {
-            in_stream_.clear_wrapped_buf();
+        char* resized_buf = (char*)mem_realloc(file_data_buf, read_size);
+        if (IS_NULL(resized_buf)) {
             return E_OOM;
         }
+        file_data_buf = resized_buf;
         file_data_buf_size = read_size;
         // Update stream pointer immediately so it stays valid even if
         // the subsequent read fails and the caller frees via destroy().
@@ -1190,18 +1202,20 @@ int AlignedChunkReader::load_by_aligned_meta_multi(
     ret = read_file_->read(time_chunk_meta_->offset_of_chunk_header_,
                            time_file_data_buf, file_data_time_buf_size_,
                            ret_read_len);
-    if (IS_SUCC(ret) && ret_read_len < ChunkHeader::MIN_SERIALIZED_SIZE) {
+    if (!IS_SUCC(ret)) {
+        mem_free(time_file_data_buf);
+        return ret;
+    }
+    if (ret_read_len < ChunkHeader::MIN_SERIALIZED_SIZE) {
         ret = E_TSFILE_CORRUPTED;
         mem_free(time_file_data_buf);
         return ret;
     }
-    if (IS_SUCC(ret)) {
-        time_in_stream_.wrap_from(time_file_data_buf, ret_read_len);
-        if (RET_FAIL(time_chunk_header_.deserialize_from(time_in_stream_))) {
-            return ret;
-        }
-        time_chunk_visit_offset_ = time_in_stream_.read_pos();
+    time_in_stream_.wrap_from(time_file_data_buf, ret_read_len);
+    if (RET_FAIL(time_chunk_header_.deserialize_from(time_in_stream_))) {
+        return ret;
     }
+    time_chunk_visit_offset_ = time_in_stream_.read_pos();
 
     // Alloc time decoder/compressor
     if (IS_SUCC(ret)) {
@@ -1236,24 +1250,25 @@ int AlignedChunkReader::load_by_aligned_meta_multi(
 
         ret = read_file_->read(col->chunk_meta->offset_of_chunk_header_, vbuf,
                                col->file_data_buf_size, ret_read_len);
-        if (IS_SUCC(ret) && ret_read_len < ChunkHeader::MIN_SERIALIZED_SIZE) {
+        if (!IS_SUCC(ret)) {
+            mem_free(vbuf);
+            return ret;
+        }
+        if (ret_read_len < ChunkHeader::MIN_SERIALIZED_SIZE) {
             ret = E_TSFILE_CORRUPTED;
             mem_free(vbuf);
+            return ret;
+        }
+        col->in_stream.wrap_from(vbuf, ret_read_len);
+        if (RET_FAIL(col->chunk_header.deserialize_from(col->in_stream))) {
             break;
         }
-        if (IS_SUCC(ret)) {
-            col->in_stream.wrap_from(vbuf, ret_read_len);
-            if (RET_FAIL(col->chunk_header.deserialize_from(col->in_stream))) {
-                break;
-            }
-            col->chunk_visit_offset = col->in_stream.read_pos();
-            if (RET_FAIL(alloc_compressor_and_decoder(
-                    col->decoder, col->compressor,
-                    col->chunk_header.encoding_type_,
-                    col->chunk_header.data_type_,
-                    col->chunk_header.compression_type_))) {
-                break;
-            }
+        col->chunk_visit_offset = col->in_stream.read_pos();
+        if (RET_FAIL(alloc_compressor_and_decoder(
+                col->decoder, col->compressor, 
col->chunk_header.encoding_type_,
+                col->chunk_header.data_type_,
+                col->chunk_header.compression_type_))) {
+            break;
         }
     }
 
diff --git a/cpp/src/reader/chunk_reader.cc b/cpp/src/reader/chunk_reader.cc
index f127ee2ee..ce8d87a58 100644
--- a/cpp/src/reader/chunk_reader.cc
+++ b/cpp/src/reader/chunk_reader.cc
@@ -254,11 +254,16 @@ int ChunkReader::read_from_file_and_rewrap(int want_size) 
{
         (want_size < DEFAULT_READ_SIZE ? DEFAULT_READ_SIZE : want_size);
     if (file_data_buf_size_ < read_size ||
         read_size < file_data_buf_size_ / 10) {
-        file_data_buf = (char*)mem_realloc(file_data_buf, read_size);
-        if (IS_NULL(file_data_buf)) {
+        char* resized_buf = (char*)mem_realloc(file_data_buf, read_size);
+        if (IS_NULL(resized_buf)) {
             return E_OOM;
         }
+        file_data_buf = resized_buf;
         file_data_buf_size_ = read_size;
+        // mem_realloc() may move the allocation. Keep the stream's external
+        // pointer synchronized even if the subsequent file read fails, so
+        // reset()/destroy() can still release the live buffer.
+        in_stream_.wrap_from(file_data_buf, read_size);
     }
     int ret_read_len = 0;
     if (RET_FAIL(
diff --git a/cpp/test/common/container/array_test.cc 
b/cpp/test/common/container/array_test.cc
index 661ac2c2c..cda5b596d 100644
--- a/cpp/test/common/container/array_test.cc
+++ b/cpp/test/common/container/array_test.cc
@@ -165,4 +165,16 @@ TEST_F(ArrayTest, CapacityShrink) {
     EXPECT_EQ(arr.size(), 0);
 }
 
-}  // namespace common
\ No newline at end of file
+TEST_F(ArrayTest, ReallocFailurePreservesAllocation) {
+    common::Array<int> arr(1);
+    ASSERT_EQ(arr.init(), E_OK);
+    ASSERT_EQ(arr.append(7), E_OK);
+
+    common::TEST_fail_next_mem_realloc();
+    EXPECT_EQ(arr.append(8), E_OOM);
+    EXPECT_EQ(arr.size(), 1);
+    EXPECT_EQ(arr.capacity(), 1);
+    EXPECT_EQ(arr[0], 7);
+}
+
+}  // namespace common
diff --git a/cpp/test/common/container/byte_buffer_test.cc 
b/cpp/test/common/container/byte_buffer_test.cc
index 4f9c63f06..a28c3c650 100644
--- a/cpp/test/common/container/byte_buffer_test.cc
+++ b/cpp/test/common/container/byte_buffer_test.cc
@@ -66,4 +66,21 @@ TEST_F(ByteBufferTest, ExtendMemory) {
     EXPECT_STREQ(read_value, value);
 }
 
-}  // namespace
\ No newline at end of file
+TEST_F(ByteBufferTest, ReallocFailurePreservesAllocation) {
+    common::ByteBuffer byte_buffer;
+    byte_buffer.init(4);
+    const char first[] = {'a', 'b', 'c', 'd'};
+    const char second[] = {'e', 'f', 'g', 'h'};
+    ASSERT_EQ(byte_buffer.append_fixed_value(first, sizeof(first)),
+              common::E_OK);
+    char* original_data = byte_buffer.get_data();
+
+    common::TEST_fail_next_mem_realloc();
+    EXPECT_EQ(byte_buffer.append_fixed_value(second, sizeof(second)),
+              common::E_OOM);
+    EXPECT_EQ(byte_buffer.get_data(), original_data);
+    EXPECT_EQ(byte_buffer.get_data_size(), sizeof(first));
+    EXPECT_EQ(memcmp(byte_buffer.get_data(), first, sizeof(first)), 0);
+}
+
+}  // namespace
diff --git a/cpp/test/common/container/sorted_array_test.cc 
b/cpp/test/common/container/sorted_array_test.cc
index 85e55dd46..fce8f806a 100644
--- a/cpp/test/common/container/sorted_array_test.cc
+++ b/cpp/test/common/container/sorted_array_test.cc
@@ -154,4 +154,17 @@ TEST(SortedArrayTest, ShrinkArray) {
     array.destroy();
 }
 
+TEST(SortedArrayTest, ReallocFailurePreservesAllocation) {
+    SortedArray<int> array(1);
+    ASSERT_EQ(array.init(), E_OK);
+    ASSERT_EQ(array.insert(7), E_OK);
+
+    common::TEST_fail_next_mem_realloc();
+    EXPECT_EQ(array.insert(8), E_OOM);
+    EXPECT_EQ(array.size(), 1);
+    EXPECT_EQ(array.capacity(), 1);
+    EXPECT_EQ(array[0], 7);
+    array.destroy();
+}
+
 }  // namespace common
diff --git a/cpp/test/common/tablet_test.cc b/cpp/test/common/tablet_test.cc
index 11dfa485f..ce75ca2ee 100644
--- a/cpp/test/common/tablet_test.cc
+++ b/cpp/test/common/tablet_test.cc
@@ -148,6 +148,29 @@ TEST(TabletTest, StringRepeatedTotalBytesOverflowRejected) 
{
               common::E_OVERFLOW);
 }
 
+TEST(TabletTest, StringReallocFailureReturnsOomAndPreservesColumn) {
+    std::vector<MeasurementSchema> schema_vec;
+    schema_vec.push_back(MeasurementSchema(
+        "m_str", common::TSDataType::STRING, common::TSEncoding::PLAIN,
+        common::CompressionType::UNCOMPRESSED));
+    Tablet tablet("dev",
+                  std::make_shared<std::vector<MeasurementSchema>>(schema_vec),
+                  1u);
+
+    std::string oversized_value(64, 'x');
+    common::TEST_fail_next_mem_realloc();
+    EXPECT_EQ(tablet.add_value(0u, 0u, common::String(oversized_value)),
+              common::E_OOM);
+
+    common::String value("ok", 2);
+    ASSERT_EQ(tablet.add_value(0u, 0u, value), common::E_OK);
+    common::TSDataType type;
+    auto* stored = static_cast<common::String*>(tablet.get_value(0u, 0u, 
type));
+    ASSERT_NE(stored, nullptr);
+    EXPECT_EQ(stored->len_, 2u);
+    EXPECT_EQ(memcmp(stored->buf_, "ok", 2), 0);
+}
+
 // Regression: set_column_string_values only checked offsets[count] before;
 // non-monotonic / negative / non-zero-start offsets would underflow the
 // downstream `offsets[i+1] - offsets[i]` length calc and trigger wild
@@ -199,4 +222,4 @@ TEST(TabletTest, LargeQuantities) {
     EXPECT_EQ(tablet.get_column_count(), schema_vec.size());
 }
 
-}  // namespace storage
\ No newline at end of file
+}  // namespace storage
diff --git a/cpp/test/compress/lzo_compressor_test.cc 
b/cpp/test/compress/lzo_compressor_test.cc
index efc5534dc..88179b573 100644
--- a/cpp/test/compress/lzo_compressor_test.cc
+++ b/cpp/test/compress/lzo_compressor_test.cc
@@ -126,4 +126,72 @@ TEST_F(LZOTest, TestBytes2) {
     compressor.after_compress(compressed_buf);
     compressor.after_uncompress(decompressed_buf);
 }
+
+TEST_F(LZOTest, DestroyAfterReleasedBuffersIsSafe) {
+    storage::LZOCompressor compressor;
+    std::string input(4096, 'L');
+    char* compressed = nullptr;
+    uint32_t compressed_len = 0;
+    ASSERT_EQ(compressor.compress(&input[0], input.size(), compressed,
+                                  compressed_len),
+              common::E_OK);
+
+    char* uncompressed = nullptr;
+    uint32_t uncompressed_len = 0;
+    ASSERT_EQ(compressor.uncompress(compressed, compressed_len, uncompressed,
+                                    uncompressed_len),
+              common::E_OK);
+    compressor.after_compress(compressed);
+    compressor.after_uncompress(uncompressed);
+
+    compressor.destroy();
+    compressor.destroy();
+}
+
+TEST_F(LZOTest, CompressReallocFailureReleasesTemporaryBuffer) {
+    storage::LZOCompressor compressor;
+    std::string input(1024, 'L');
+    char* compressed = nullptr;
+    uint32_t compressed_len = 0;
+    int64_t memory_before =
+        common::ModStat::get_instance().get_stat(common::MOD_COMPRESSOR_OBJ);
+
+    common::TEST_fail_next_mem_realloc();
+    EXPECT_EQ(compressor.compress(&input[0], input.size(), compressed,
+                                  compressed_len),
+              common::E_OOM);
+    EXPECT_EQ(compressed, nullptr);
+    EXPECT_EQ(compressed_len, 0u);
+    EXPECT_EQ(
+        common::ModStat::get_instance().get_stat(common::MOD_COMPRESSOR_OBJ),
+        memory_before);
+    compressor.destroy();
+}
+
+TEST_F(LZOTest, UncompressReallocFailureReleasesTemporaryBuffer) {
+    storage::LZOCompressor compressor;
+    std::string input(1024, 'L');
+    char* compressed = nullptr;
+    uint32_t compressed_len = 0;
+    ASSERT_EQ(compressor.compress(&input[0], input.size(), compressed,
+                                  compressed_len),
+              common::E_OK);
+
+    char* uncompressed = nullptr;
+    uint32_t uncompressed_len = 0;
+    int64_t memory_before =
+        common::ModStat::get_instance().get_stat(common::MOD_COMPRESSOR_OBJ);
+    common::TEST_fail_next_mem_realloc();
+    EXPECT_EQ(compressor.uncompress(compressed, compressed_len, uncompressed,
+                                    uncompressed_len),
+              common::E_OOM);
+    EXPECT_EQ(uncompressed, nullptr);
+    EXPECT_EQ(uncompressed_len, 0u);
+    EXPECT_EQ(
+        common::ModStat::get_instance().get_stat(common::MOD_COMPRESSOR_OBJ),
+        memory_before);
+
+    compressor.after_compress(compressed);
+    compressor.destroy();
+}
 }  // namespace
diff --git a/cpp/test/compress/snappy_compressor_test.cc 
b/cpp/test/compress/snappy_compressor_test.cc
index 249200cce..5ad3a569d 100644
--- a/cpp/test/compress/snappy_compressor_test.cc
+++ b/cpp/test/compress/snappy_compressor_test.cc
@@ -162,4 +162,24 @@ TEST_F(SnappyTest, AfterUncompressFreesParamNotMember) {
     compressor.after_compress(compressed_a);
     compressor.after_compress(compressed_b);
 }
+
+TEST_F(SnappyTest, ReallocFailureReleasesTemporaryBuffer) {
+    storage::SnappyCompressor compressor;
+    std::string input(1024, 'S');
+    char* compressed = nullptr;
+    uint32_t compressed_len = 0;
+    int64_t memory_before =
+        common::ModStat::get_instance().get_stat(common::MOD_COMPRESSOR_OBJ);
+
+    common::TEST_fail_next_mem_realloc();
+    EXPECT_EQ(compressor.compress(&input[0], input.size(), compressed,
+                                  compressed_len),
+              common::E_OOM);
+    EXPECT_EQ(compressed, nullptr);
+    EXPECT_EQ(compressed_len, 0u);
+    EXPECT_EQ(
+        common::ModStat::get_instance().get_stat(common::MOD_COMPRESSOR_OBJ),
+        memory_before);
+    compressor.destroy();
+}
 }  // namespace
diff --git a/cpp/test/reader/chunk_reader_resource_test.cc 
b/cpp/test/reader/chunk_reader_resource_test.cc
new file mode 100644
index 000000000..11d456a4d
--- /dev/null
+++ b/cpp/test/reader/chunk_reader_resource_test.cc
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <cstdio>
+#include <fstream>
+#include <new>
+#include <string>
+#include <vector>
+
+#include "common/allocator/alloc_base.h"
+#include "reader/aligned_chunk_reader.h"
+
+namespace storage {
+namespace {
+
+class TempTsFile {
+   public:
+    explicit TempTsFile(const char* path) : path_(path) {
+        std::remove(path_.c_str());
+        std::ofstream out(path_, std::ios::binary | std::ios::trunc);
+        out.write(MAGIC_STRING_TSFILE, MAGIC_STRING_TSFILE_LEN);
+        out.put(VERSION_NUM_BYTE);
+        out.write(MAGIC_STRING_TSFILE, MAGIC_STRING_TSFILE_LEN);
+    }
+
+    ~TempTsFile() { std::remove(path_.c_str()); }
+
+    const std::string& path() const { return path_; }
+
+   private:
+    std::string path_;
+};
+
+AlignedChunkReader* allocate_reader(ReadFile* read_file) {
+    void* memory =
+        common::mem_alloc(sizeof(AlignedChunkReader), 
common::MOD_CHUNK_READER);
+    if (memory == nullptr) {
+        return nullptr;
+    }
+    auto* reader = new (memory) AlignedChunkReader();
+    if (reader->init(read_file, common::String("s", 1), common::INT64,
+                     nullptr) != common::E_OK) {
+        reader->destroy();
+        common::mem_free(reader);
+        return nullptr;
+    }
+    return reader;
+}
+
+void free_reader(AlignedChunkReader* reader) {
+    reader->destroy();
+    common::mem_free(reader);
+}
+
+TEST(ChunkReaderResourceTest, AlignedInitialShortReadReleasesBuffer) {
+    TempTsFile temp_file("aligned_initial_short_read.tsfile");
+    ReadFile read_file;
+    ASSERT_EQ(read_file.open(temp_file.path()), common::E_OK);
+    AlignedChunkReader* reader = allocate_reader(&read_file);
+    ASSERT_NE(reader, nullptr);
+    ChunkMeta time_meta;
+    time_meta.offset_of_chunk_header_ = read_file.file_size();
+    ChunkMeta value_meta;
+    int64_t memory_before =
+        common::ModStat::get_instance().get_stat(common::MOD_CHUNK_READER);
+
+    EXPECT_EQ(reader->load_by_aligned_meta(&time_meta, &value_meta),
+              common::E_TSFILE_CORRUPTED);
+    EXPECT_EQ(
+        common::ModStat::get_instance().get_stat(common::MOD_CHUNK_READER),
+        memory_before);
+
+    free_reader(reader);
+}
+
+TEST(ChunkReaderResourceTest, MultiAlignedInitialShortReadReleasesBuffer) {
+    TempTsFile temp_file("multi_aligned_initial_short_read.tsfile");
+    ReadFile read_file;
+    ASSERT_EQ(read_file.open(temp_file.path()), common::E_OK);
+    AlignedChunkReader* reader = allocate_reader(&read_file);
+    ASSERT_NE(reader, nullptr);
+    ChunkMeta time_meta;
+    time_meta.offset_of_chunk_header_ = read_file.file_size();
+    ChunkMeta value_meta;
+    std::vector<ChunkMeta*> value_metas{&value_meta};
+    int64_t memory_before =
+        common::ModStat::get_instance().get_stat(common::MOD_CHUNK_READER);
+
+    EXPECT_EQ(reader->load_by_aligned_meta_multi(&time_meta, value_metas),
+              common::E_TSFILE_CORRUPTED);
+    EXPECT_EQ(
+        common::ModStat::get_instance().get_stat(common::MOD_CHUNK_READER),
+        memory_before);
+
+    free_reader(reader);
+}
+
+}  // namespace
+}  // namespace storage

Reply via email to