From 41961429743ad1a8c5e4c1afebfdb4a35af65e99 Mon Sep 17 00:00:00 2001 From: Zehuan Shi Date: Sat, 15 Aug 2026 00:17:31 +0800 Subject: [PATCH 1/2] LSMT: Add hybrid writable layer type Add an optional hybrid writable-layer type and persist it in the layer header. Keep append RW as the default. Signed-off-by: Zehuan Shi --- src/overlaybd/lsmt/file.cpp | 52 ++++++++++++++++++------- src/overlaybd/lsmt/file.h | 8 +++- src/overlaybd/lsmt/format_spec.md | 4 +- src/overlaybd/lsmt/test/lsmt-filetest.h | 6 +-- src/overlaybd/lsmt/test/test.cpp | 9 ++++- src/test/image_service_test.cpp | 2 +- src/tools/overlaybd-create.cpp | 10 ++++- 7 files changed, 68 insertions(+), 23 deletions(-) diff --git a/src/overlaybd/lsmt/file.cpp b/src/overlaybd/lsmt/file.cpp index 0bac3181..dafaebe7 100644 --- a/src/overlaybd/lsmt/file.cpp +++ b/src/overlaybd/lsmt/file.cpp @@ -84,6 +84,7 @@ struct HeaderTrailer { static const uint32_t FLAG_SHIFT_TYPE = 1; // 1:data file, 0:index file static const uint32_t FLAG_SHIFT_SEALED = 2; // 1:YES, 0:NO static const uint32_t FLAG_SPARSE_RW = 4; // 1:sparse file 0:normal file + static const uint32_t FLAG_HYBRID_RW = 5; // 1:hybrid rw 0:append rw uint32_t get_flag_bit(uint32_t shift) const { return flags & (1 << shift); @@ -112,6 +113,9 @@ struct HeaderTrailer { bool is_sparse_rw() const { return get_flag_bit(FLAG_SPARSE_RW); } + bool is_hybrid_rw() const { + return get_flag_bit(FLAG_HYBRID_RW); + } void set_header() { set_flag_bit(FLAG_SHIFT_HEADER); @@ -142,6 +146,12 @@ struct HeaderTrailer { void clr_sparse_rw() { clr_flag_bit(FLAG_SPARSE_RW); } + void set_hybrid_rw() { + set_flag_bit(FLAG_HYBRID_RW); + } + void clr_hybrid_rw() { + clr_flag_bit(FLAG_HYBRID_RW); + } int set_tag(char *buf, size_t n) { if (n > TAG_SIZE) { @@ -203,10 +213,14 @@ static int write_header_trailer(IFile *file, bool is_header, bool is_sealed, boo pht->set_data_file(); else pht->set_index_file(); - if (args.sparse_rw) + if (args.rw_type == RWType::Sparse) pht->set_sparse_rw(); else pht->clr_sparse_rw(); + if (args.rw_type == RWType::Hybrid) + pht->set_hybrid_rw(); + else + pht->clr_hybrid_rw(); pht->index_offset = index_offset; pht->index_size = index_size; @@ -345,7 +359,8 @@ static int load_layer_info(IFile **src_files, size_t n, LayerInfo &layer, bool o } HeaderTrailer *pht = (HeaderTrailer *)buf_top; layer.virtual_size = pht->virtual_size; - layer.sparse_rw = pht->is_sparse_rw(); + layer.rw_type = pht->is_sparse_rw() ? RWType::Sparse + : (pht->is_hybrid_rw() ? RWType::Hybrid : RWType::Append); if (n != 1) { ALIGNED_MEM(buf_bottom, HeaderTrailer::SPACE, ALIGNMENT4K); // @@ -384,7 +399,7 @@ static int compact(const CompactOptions &opt, atomic_uint64_t &compacted_idx_siz LayerInfo layer; if (load_layer_info(src_files, opt.n, layer) != 0) return -1; - layer.sparse_rw = false; + layer.rw_type = RWType::Append; layer.user_tag = commit_args->user_tag; layer.uuid.clear(); if (UUID::String::is_valid((commit_args->uuid).c_str())) { @@ -1465,6 +1480,9 @@ IFileRO *open_file_ro(IFile *file, bool ownership) { IFileRW *open_file_rw(IFile *fdata, IFile *findex, bool ownership) { ALIGNED_MEM(buf, HeaderTrailer::SPACE, ALIGNMENT4K); auto pht = verify_ht(fdata, buf); + if (pht && pht->is_sparse_rw() && pht->is_hybrid_rw()) { + LOG_ERROR_RETURN(EINVAL, nullptr, "invalid writable layer type flags"); + } if ((pht == nullptr) || ((pht->is_sparse_rw() == false) && (!findex))) { LOG_ERRNO_RETURN(0, nullptr, "invalid file ptr, fdata: ` findex: `", fdata, findex); } @@ -1501,7 +1519,7 @@ IFileRW *open_file_rw(IFile *fdata, IFile *findex, bool ownership) { } LSMTFile *rst = nullptr; if (pht->is_sparse_rw() == false) { - LOG_INFO("create LSMTFile object (append-only)"); + LOG_INFO("create LSMTFile object (hybrid_rw: `)", pht->is_hybrid_rw()); rst = new LSMTFile; } else { LOG_INFO("create LSMTSparseFile object"); @@ -1515,20 +1533,25 @@ IFileRW *open_file_rw(IFile *fdata, IFile *findex, bool ownership) { UUID raw; raw.parse(pht->uuid); rst->m_uuid.push_back(raw); - LOG_INFO("Layer Info: { UUID:` , Parent_UUID: `, SparseRW: `, Virtual size: `, Version: `.` }", - pht->uuid, pht->parent_uuid, pht->is_sparse_rw(), rst->m_vsize, pht->version, - pht->sub_version); + LOG_INFO("Layer Info: { UUID:` , Parent_UUID: `, SparseRW: `, HybridRW: `, Virtual size: `, Version: `.` }", + pht->uuid, pht->parent_uuid, pht->is_sparse_rw(), pht->is_hybrid_rw(), rst->m_vsize, + pht->version, pht->sub_version); return rst; } IFileRW *create_file_rw(const LayerInfo &args, bool ownership) { auto fdata = args.fdata; auto findex = args.findex; - if ((args.sparse_rw == false) && (!fdata || !findex)) { + if (args.rw_type != RWType::Append && args.rw_type != RWType::Hybrid && + args.rw_type != RWType::Sparse) { + LOG_ERROR_RETURN(EINVAL, nullptr, "invalid writable layer type: `", + static_cast(args.rw_type)); + } + if ((args.rw_type != RWType::Sparse) && (!fdata || !findex)) { LOG_ERROR_RETURN(0, nullptr, "invalid file ptr, fdata: `, findex: `", fdata, findex); } LSMTFile *rst = nullptr; - if (args.sparse_rw == false) { + if (args.rw_type != RWType::Sparse) { rst = new LSMTFile; } else { rst = new LSMTSparseFile; @@ -1544,14 +1567,15 @@ IFileRW *create_file_rw(const LayerInfo &args, bool ownership) { rst->m_vsize = args.virtual_size; rst->m_file_ownership = ownership; write_header_trailer(fdata, true, false, true, 0, 0, args); - if (!args.sparse_rw) { + if (args.rw_type != RWType::Sparse) { write_header_trailer(findex, true, false, false, HeaderTrailer::SPACE, 0, args); } HeaderTrailer tmp; // args.parent_uuid.to_string(parent_uuid, UUID::String::LEN); - LOG_INFO("Layer Info: { UUID:`, Parent_UUID: `, Sparse: ` Virtual size: `, Version: `.` }", raw, - args.parent_uuid, args.sparse_rw, rst->m_vsize, tmp.version, tmp.sub_version); - if (args.sparse_rw) { + LOG_INFO("Layer Info: { UUID:`, Parent_UUID: `, RWType: ` Virtual size: `, Version: `.` }", raw, + args.parent_uuid, static_cast(args.rw_type), rst->m_vsize, tmp.version, + tmp.sub_version); + if (args.rw_type == RWType::Sparse) { fdata->ftruncate(args.virtual_size + HeaderTrailer::SPACE); } return rst; @@ -1561,7 +1585,7 @@ IFileRW *create_warpfile(WarpFileArgs &args, bool ownership) { auto rst = new LSMTWarpFile(); rst->m_findex = args.findex; LayerInfo info; - info.sparse_rw = false; + info.rw_type = RWType::Append; info.virtual_size = args.virtual_size; info.parent_uuid.parse(args.parent_uuid); info.uuid.parse(args.uuid); diff --git a/src/overlaybd/lsmt/file.h b/src/overlaybd/lsmt/file.h index f49f995f..ae853d2a 100644 --- a/src/overlaybd/lsmt/file.h +++ b/src/overlaybd/lsmt/file.h @@ -113,6 +113,12 @@ class IFileRW : public IFileRO { virtual int restack(IFileRW *upper) = 0; }; +enum class RWType : uint8_t { + Append, + Hybrid, + Sparse, +}; + // create a new writable LSMT file constitued by a data file and an index file, // optionally obtaining the ownerships of the underlying files, // thus they will be destructed automatically. @@ -123,7 +129,7 @@ struct LayerInfo { UUID parent_uuid; UUID uuid; char *user_tag = nullptr; // a user provided string of message, 256B at most - bool sparse_rw = false; + RWType rw_type = RWType::Append; size_t len = 0; // len of user_tag; if it's 0, it will be detected with strlen() LayerInfo(photon::fs::IFile *_fdata = nullptr, photon::fs::IFile *_findex = nullptr) : fdata(_fdata), findex(_findex) { diff --git a/src/overlaybd/lsmt/format_spec.md b/src/overlaybd/lsmt/format_spec.md index 6a0fd1dd..5682c7a1 100644 --- a/src/overlaybd/lsmt/format_spec.md +++ b/src/overlaybd/lsmt/format_spec.md @@ -40,7 +40,7 @@ The format of header is described as below. All fields are little-endian. | sealed | 2 | this file is sealed (1) or not (0) | | gc_layer | 3 | this is a gc layer (1) or normal layer (0) | | sparse_rw | 4 | this is a sparse rw layer | -| info_valid | 5 | information validity of the fields *after* flags (they were initially invalid (0) after creation; and readers must resort to trailer when they meet such headers) | +| hybrid_rw | 5 | this is a hybrid rw layer | | reserved | 6~31 | reserved for future use; must be 0s | @@ -67,4 +67,4 @@ a 128-bit struct defined as below: ## trailer An updated edition of header, in the same format. Trailer is useful in append-only storage during creation of the blob. Use trailer whenever -possible. \ No newline at end of file +possible. diff --git a/src/overlaybd/lsmt/test/lsmt-filetest.h b/src/overlaybd/lsmt/test/lsmt-filetest.h index 384b9b5b..c8448066 100644 --- a/src/overlaybd/lsmt/test/lsmt-filetest.h +++ b/src/overlaybd/lsmt/test/lsmt-filetest.h @@ -120,7 +120,7 @@ class FileTest : public ::testing::Test { snprintf_names(next_layer_id++); } - IFileRW *create_file_rw(bool sparse = false) { + IFileRW *create_file_rw(bool sparse = false, bool hybrid = false) { name_next_layer(); auto fdata = lfs->open(data_name.back().c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRWXU); // auto fdata = photon::fs::open_localfile_adaptor(data_name.back().c_str(), O_RDWR | @@ -132,7 +132,7 @@ class FileTest : public ::testing::Test { EXPECT_EQ(nullptr, ::create_file_rw(_, true)); LOG_INFO("TEST OK"); LayerInfo args(fdata, findex); - args.sparse_rw = sparse; + args.rw_type = hybrid ? RWType::Hybrid : (sparse ? RWType::Sparse : RWType::Append); if (parent_uuid != "") args.parent_uuid.parse(parent_uuid.c_str(), parent_uuid.size()); args.virtual_size = vsize; @@ -425,7 +425,7 @@ class FileTest3 : public FileTest2 { LOG_INFO("data: ` index: `", data_name.back().c_str(), idx_name.back().c_str()); LayerInfo args(fdata, findex); if (sparse) - args.sparse_rw = true; + args.rw_type = RWType::Sparse; if (parent_uuid != "") args.parent_uuid.parse(parent_uuid.c_str(), parent_uuid.size()); diff --git a/src/overlaybd/lsmt/test/test.cpp b/src/overlaybd/lsmt/test/test.cpp index 0bc85240..a12fe6e5 100644 --- a/src/overlaybd/lsmt/test/test.cpp +++ b/src/overlaybd/lsmt/test/test.cpp @@ -406,6 +406,13 @@ TEST_F(FileTest, create_open_sp) { delete file2; } +TEST_F(FileTest, create_open_hybrid) { + auto file1 = create_file_rw(/*sparse = */ false, /*hybrid = */ true); + delete file1; + auto file2 = open_file_rw(); + delete file2; +} + class FileTest1 : public FileTest { public: virtual void SetUp() override { @@ -428,7 +435,7 @@ TEST_F(FileTest2, sparse_rw) { } LayerInfo args; args.fdata = file; - args.sparse_rw = true; + args.rw_type = RWType::Sparse; args.virtual_size = 64 << 20; auto layer = ::create_file_rw(args, true); char raw_data[65536]; diff --git a/src/test/image_service_test.cpp b/src/test/image_service_test.cpp index ae1a8a5f..e57724a4 100644 --- a/src/test/image_service_test.cpp +++ b/src/test/image_service_test.cpp @@ -371,7 +371,7 @@ class CreateSnapshotTest : public DevIDRegisterTest { auto fdata = photon::fs::open_localfile_adaptor(data_name, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU); auto findex = photon::fs::open_localfile_adaptor(index_name, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU); LSMT::LayerInfo args(fdata, findex); - args.sparse_rw = sparse; + args.rw_type = sparse ? LSMT::RWType::Sparse : LSMT::RWType::Append; args.virtual_size = 64 << 20; auto file = LSMT::create_file_rw(args, true); delete file; diff --git a/src/tools/overlaybd-create.cpp b/src/tools/overlaybd-create.cpp index ffa7dda5..c2c86d15 100644 --- a/src/tools/overlaybd-create.cpp +++ b/src/tools/overlaybd-create.cpp @@ -49,6 +49,7 @@ int main(int argc, char **argv) { uint64_t vsize; string parent_uuid; bool sparse = false; + bool hybrid = false; std::string data_file_path, index_file_path, warp_index_path; bool build_turboOCI = false; bool build_fastoci = false; @@ -59,6 +60,7 @@ int main(int argc, char **argv) { CLI::App app{"this is overlaybd-create"}; app.add_option("-u", parent_uuid, "parent uuid"); app.add_flag("-s", sparse, "create sparse RW layer")->default_val(false); + app.add_flag("--hybrid", hybrid, "create hybrid RW layer")->default_val(false); app.add_flag("--turboOCI", build_turboOCI, "commit using turboOCI format")->default_val(false); app.add_flag("--fastoci", build_fastoci, "commit using turboOCI format(depracated)")->default_val(false); app.add_flag("--raw", raw, "create raw image")->default_val(false); @@ -69,6 +71,11 @@ int main(int argc, char **argv) { app.add_flag("--verbose", verbose, "output debug info")->default_val(false); CLI11_PARSE(app, argc, argv); + if (sparse && hybrid) { + fprintf(stderr, "-s and --hybrid cannot be used together\n"); + return -1; + } + build_turboOCI = build_turboOCI || build_fastoci; set_log_output_level(verbose ? 0 : 1); @@ -93,7 +100,8 @@ int main(int argc, char **argv) { LSMT::LayerInfo args(fdata, findex); args.parent_uuid.parse(parent_uuid.c_str(), parent_uuid.size()); args.virtual_size = vsize; - args.sparse_rw = sparse; + args.rw_type = sparse ? LSMT::RWType::Sparse + : (hybrid ? LSMT::RWType::Hybrid : LSMT::RWType::Append); file = LSMT::create_file_rw(args, false); } From 5da458c5b0c6ebe270172cfb699b9614045b1fdf Mon Sep 17 00:00:00 2001 From: Zehuan Shi Date: Sat, 15 Aug 2026 00:17:37 +0800 Subject: [PATCH 2/2] LSMT: Reuse writable layer data in hybrid mode Reuse existing non-zero mappings in the current writable layer while continuing to append uncovered, zeroed, and lower-layer ranges. Signed-off-by: Zehuan Shi --- src/overlaybd/lsmt/file.cpp | 76 +++++-- src/overlaybd/lsmt/index.cpp | 34 +++ src/overlaybd/lsmt/index.h | 13 ++ src/overlaybd/lsmt/test/test.cpp | 375 ++++++++++++++++++++++++++++++- 4 files changed, 481 insertions(+), 17 deletions(-) diff --git a/src/overlaybd/lsmt/file.cpp b/src/overlaybd/lsmt/file.cpp index dafaebe7..b764e136 100644 --- a/src/overlaybd/lsmt/file.cpp +++ b/src/overlaybd/lsmt/file.cpp @@ -739,6 +739,7 @@ class LSMTFile : public LSMTReadOnlyFile { uint64_t m_data_offset = HeaderTrailer::SPACE / ALIGNMENT; uint8_t m_rw_tag = 0; + bool m_hybrid_rw = false; Mutex m_rw_mtx; IFile *m_findex = nullptr; @@ -765,6 +766,11 @@ class LSMTFile : public LSMTReadOnlyFile { return nullptr; } + IComboIndex *rw_index() const { + assert(m_index != nullptr); + return static_cast(m_index); + } + virtual int vioctl(int request, va_list args) override { if (request == GetType) { return LSMTReadOnlyFile::vioctl(request, args); @@ -862,26 +868,66 @@ class LSMTFile : public LSMTReadOnlyFile { offset += MAX_IO_SIZE; } // wait unlock - off_t moffset = -1; { Lock lock(m_rw_mtx); - moffset = append(m_files[m_rw_tag], buf, count); - if (moffset == 0) - return -1; m_vsize = max(m_vsize, count + offset); if (m_vsize < count + offset) { LOG_INFO("resize m_visze: `->`", m_vsize, count + offset); } - SegmentMapping m{ - (uint64_t)offset / (uint64_t)ALIGNMENT, - (uint32_t)count / (uint32_t)ALIGNMENT, - (uint64_t)moffset / (uint64_t)ALIGNMENT, + auto offset_in_blocks = (uint64_t)offset / ALIGNMENT; + auto count_in_blocks = (uint32_t)count / ALIGNMENT; + auto end_in_blocks = offset_in_blocks + count_in_blocks; + unique_ptr writable_layer_cursor; + if (m_hybrid_rw) { + writable_layer_cursor = rw_index()->writable_layer_cursor( + {offset_in_blocks, count_in_blocks}); + } + + auto update_mapping = [&](SegmentMapping m, bool append_data) -> int { + auto data_offset = (m.offset - offset_in_blocks) * ALIGNMENT; + auto data_length = (size_t)m.length * ALIGNMENT; + if (!append_data) { + if (m_files[m_rw_tag]->pwrite((const char *)buf + data_offset, data_length, + m.moffset * ALIGNMENT) != (ssize_t)data_length) { + LOG_ERRNO_RETURN(0, -1, "rewrite data failed"); + } + return 0; + } + + auto moffset = append(m_files[m_rw_tag], (const char *)buf + data_offset, + data_length); + if (moffset == 0) + return -1; + m.moffset = (uint64_t)moffset / ALIGNMENT; + m_data_offset = max(m_data_offset, m.mend()); + m.tag = m_rw_tag; + rw_index()->insert(m); + append_index(m); + return 0; }; - m.tag = m_rw_tag; - assert(m.length > (uint32_t)0); - m_data_offset = m.mend(); - static_cast(m_index)->insert(m); - append_index(m); + + uint64_t cursor = offset_in_blocks; + SegmentMapping m; + while (writable_layer_cursor && writable_layer_cursor->next(m)) { + if (cursor < m.offset) { + SegmentMapping appended(cursor, (uint32_t)(m.offset - cursor), 0); + if (update_mapping(appended, true) != 0) + return -1; + } + if (m.zeroed) { + SegmentMapping appended(m.offset, m.length, 0); + if (update_mapping(appended, true) != 0) + return -1; + } else if (update_mapping(m, false) != 0) { + return -1; + } + cursor = m.end(); + } + if (cursor < end_in_blocks) { + SegmentMapping appended(cursor, (uint32_t)(end_in_blocks - cursor), 0); + if (update_mapping(appended, true) != 0) + return -1; + } } return bytes; @@ -1080,6 +1126,7 @@ class LSMTFile : public LSMTReadOnlyFile { } LOG_DEBUG("m_files.size(): `, rw_tag: `", m_files.size(), m_rw_tag); m_findex = u->m_findex; + m_hybrid_rw = u->m_hybrid_rw; m_vsize = u->m_vsize; ((IComboIndex *)m_index)->commit_index0(); @@ -1526,6 +1573,7 @@ IFileRW *open_file_rw(IFile *fdata, IFile *findex, bool ownership) { rst = new LSMTSparseFile; } rst->m_index = pi; + rst->m_hybrid_rw = pht->is_hybrid_rw(); rst->m_findex = findex; rst->m_files.push_back(fdata); rst->m_vsize = pht->virtual_size; @@ -1557,6 +1605,7 @@ IFileRW *create_file_rw(const LayerInfo &args, bool ownership) { rst = new LSMTSparseFile; } rst->m_index = create_memory_index0((const SegmentMapping *)nullptr, 0, 0, 0); + rst->m_hybrid_rw = args.rw_type == RWType::Hybrid; rst->m_findex = findex; rst->m_files.push_back(fdata); LOG_DEBUG("unparse uuid"); @@ -1906,6 +1955,7 @@ IFileRW *stack_files(IFileRW *upper_layer, IFileRO *lower_layers, bool ownership } else { rst = new LSMTSparseFile; } + rst->m_hybrid_rw = pht->is_hybrid_rw(); // TODO: also for LSMTWarpFile if (u->m_vsize == 0) { if (u->update_vsize(l->m_vsize) < 0) { diff --git a/src/overlaybd/lsmt/index.cpp b/src/overlaybd/lsmt/index.cpp index 7c1c59ec..b3778ea0 100644 --- a/src/overlaybd/lsmt/index.cpp +++ b/src/overlaybd/lsmt/index.cpp @@ -56,6 +56,36 @@ static inline size_t copy_n(IT begin, IT end, uint64_t end_offset, SegmentMappin return m; } +class WritableLayerCursor : public IWritableLayerCursor { +public: + set::const_iterator m_current; + set::const_iterator m_end; + Segment m_range; + + WritableLayerCursor(set::const_iterator current, + set::const_iterator end, Segment range) + : m_current(current), m_end(end), m_range(range) { + } + + virtual bool next(SegmentMapping &m) override { + if (m_current == m_end || m_current->offset >= m_range.end()) + return false; + m = *m_current++; + if (m.offset < m_range.offset) + m.forward_offset_to(m_range.offset); + if (m.end() > m_range.end()) + m.backward_end_to(m_range.end()); + return true; + } +}; + +static unique_ptr create_writable_layer_cursor( + const set &mapping, Segment range) { + SegmentMapping key(range.offset, range.length, 0); + return unique_ptr( + new WritableLayerCursor(mapping.lower_bound(key), mapping.end(), range)); +} + static bool verify_mapping_order(const SegmentMapping *pmappings, size_t n); bool is_avx512f_supported() { @@ -565,6 +595,10 @@ class Index0 : public IComboIndex { return m; } + virtual unique_ptr writable_layer_cursor(Segment range) const override { + return create_writable_layer_cursor(mapping, range); + } + // dump the the whole index as an array virtual SegmentMapping *dump(size_t alignment = 0) const override { auto size = mapping.size(); diff --git a/src/overlaybd/lsmt/index.h b/src/overlaybd/lsmt/index.h index ce6931c4..8b85243b 100644 --- a/src/overlaybd/lsmt/index.h +++ b/src/overlaybd/lsmt/index.h @@ -26,6 +26,7 @@ IMemoryIndex -> IMemoryIndex0 -> IComboIndex -> Index0 ( set ) -> Co #include #include #include +#include #include namespace LSMT { @@ -144,6 +145,15 @@ class IMemoryIndex0 : public IMemoryIndex { // virtual IMemoryIndex *make_read_only_index() const = 0; }; +class IWritableLayerCursor { +public: + virtual ~IWritableLayerCursor() { + } + + // Copies the next writable-layer mapping that overlaps the requested range. + virtual bool next(SegmentMapping &mapping) = 0; +}; + class IComboIndex : public IMemoryIndex0 { public: // backing index must NOT be IMemoryIndex0! @@ -152,6 +162,9 @@ class IComboIndex : public IMemoryIndex0 { virtual int front_index(const IMemoryIndex0 *fi) = 0; virtual const IMemoryIndex0 *front_index() const = 0; + // Iterates mappings that belong to the current writable layer only. + virtual std::unique_ptr writable_layer_cursor(Segment range) const = 0; + // dump index0 which needs to compact // and then clear the original index0. // virtual IMemoryIndex0* gc_index() = 0; diff --git a/src/overlaybd/lsmt/test/test.cpp b/src/overlaybd/lsmt/test/test.cpp index a12fe6e5..43129fc8 100644 --- a/src/overlaybd/lsmt/test/test.cpp +++ b/src/overlaybd/lsmt/test/test.cpp @@ -413,6 +413,377 @@ TEST_F(FileTest, create_open_hybrid) { delete file2; } +// Seed upper [0,64); rewrite [8,56) in place; then write [0,80), appending only [64,80). +TEST_F(FileTest, hybrid_rw_reuses_upper_data) { + // All ranges below are expressed in ALIGNMENT-sized blocks. + constexpr size_t first_write_start = 0; + constexpr size_t first_write_size = 64; + constexpr size_t second_write_start = 8; + constexpr size_t second_write_size = 48; + constexpr size_t third_write_start = 0; + constexpr size_t third_write_size = 80; + ALIGNED_MEM4K(first, first_write_size * ALIGNMENT); + ALIGNED_MEM4K(second, second_write_size * ALIGNMENT); + ALIGNED_MEM4K(third, third_write_size * ALIGNMENT); + ALIGNED_MEM4K(readback, third_write_size * ALIGNMENT); + memset(first, 0x11, first_write_size * ALIGNMENT); + memset(second, 0x22, second_write_size * ALIGNMENT); + memset(third, 0x33, third_write_size * ALIGNMENT); + + auto file = create_file_rw(/*sparse = */ false, /*hybrid = */ true); + ASSERT_EQ((ssize_t)(first_write_size * ALIGNMENT), + file->pwrite(first, first_write_size * ALIGNMENT, first_write_start * ALIGNMENT)); + auto data_size = file_size(lfs, data_name.back().c_str()); + auto index_size = file_size(lfs, idx_name.back().c_str()); + + // Second write is [8, 56): both sides of the first mapping must remain readable. + ASSERT_EQ((ssize_t)(second_write_size * ALIGNMENT), + file->pwrite(second, second_write_size * ALIGNMENT, + second_write_start * ALIGNMENT)); + EXPECT_EQ(data_size, file_size(lfs, data_name.back().c_str())); + EXPECT_EQ(index_size, file_size(lfs, idx_name.back().c_str())); + ASSERT_EQ((ssize_t)(first_write_size * ALIGNMENT), + file->pread(readback, first_write_size * ALIGNMENT, first_write_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(first, readback, second_write_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(second, readback + second_write_start * ALIGNMENT, + second_write_size * ALIGNMENT)); + EXPECT_EQ(0, + memcmp(first + (second_write_start + second_write_size) * ALIGNMENT, + readback + (second_write_start + second_write_size) * ALIGNMENT, + (first_write_size - second_write_start - second_write_size) * ALIGNMENT)); + + // Third write reuses [0, 64) and appends only the 16-block tail. + ASSERT_EQ((ssize_t)(third_write_size * ALIGNMENT), + file->pwrite(third, third_write_size * ALIGNMENT, third_write_start * ALIGNMENT)); + EXPECT_EQ(data_size + (third_write_size - first_write_size) * ALIGNMENT, + file_size(lfs, data_name.back().c_str())); + + ASSERT_EQ((ssize_t)(third_write_size * ALIGNMENT), + file->pread(readback, third_write_size * ALIGNMENT, third_write_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(third, readback, third_write_size * ALIGNMENT)); + delete file; + + auto reopened = open_file_rw(); + ASSERT_EQ((ssize_t)(third_write_size * ALIGNMENT), + reopened->pread(readback, third_write_size * ALIGNMENT, third_write_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(third, readback, third_write_size * ALIGNMENT)); + delete reopened; +} + +// Seed upper [32,64); write [0,96): append [0,32) and [64,96), rewrite [32,64) in place. +TEST_F(FileTest, hybrid_rw_reuses_trailing_upper_data) { + constexpr size_t initial_offset_in_blocks = 32; + constexpr size_t initial_block_count = 32; + constexpr size_t rewrite_block_count = 96; + ALIGNED_MEM4K(initial, initial_block_count * ALIGNMENT); + ALIGNED_MEM4K(rewrite, rewrite_block_count * ALIGNMENT); + ALIGNED_MEM4K(readback, rewrite_block_count * ALIGNMENT); + memset(initial, 0x11, initial_block_count * ALIGNMENT); + memset(rewrite, 0x22, rewrite_block_count * ALIGNMENT); + + auto file = create_file_rw(/*sparse = */ false, /*hybrid = */ true); + ASSERT_EQ((ssize_t)(initial_block_count * ALIGNMENT), + file->pwrite(initial, initial_block_count * ALIGNMENT, + initial_offset_in_blocks * ALIGNMENT)); + auto data_size = file_size(lfs, data_name.back().c_str()); + + // [0, 32) and [64, 96) append; [32, 64) is rewritten in place. + ASSERT_EQ((ssize_t)(rewrite_block_count * ALIGNMENT), + file->pwrite(rewrite, rewrite_block_count * ALIGNMENT, 0)); + EXPECT_EQ(data_size + (rewrite_block_count - initial_block_count) * ALIGNMENT, + file_size(lfs, data_name.back().c_str())); + ASSERT_EQ((ssize_t)(rewrite_block_count * ALIGNMENT), + file->pread(readback, rewrite_block_count * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(rewrite, readback, rewrite_block_count * ALIGNMENT)); + delete file; + + auto reopened = open_file_rw(); + ASSERT_EQ((ssize_t)(rewrite_block_count * ALIGNMENT), + reopened->pread(readback, rewrite_block_count * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(rewrite, readback, rewrite_block_count * ALIGNMENT)); + delete reopened; +} + +// Seed upper [0,64) and [128,256); write [32,192): append only the middle hole [64,128). +TEST_F(FileTest, hybrid_rw_reuses_two_upper_ranges_across_hole) { + constexpr size_t left_start = 0; + constexpr size_t left_size = 64; + constexpr size_t right_start = 128; + constexpr size_t right_size = 128; + constexpr size_t rewrite_start = 32; + constexpr size_t rewrite_size = 160; + constexpr size_t whole_range_size = right_start + right_size; + ALIGNED_MEM4K(left, left_size * ALIGNMENT); + ALIGNED_MEM4K(right, right_size * ALIGNMENT); + ALIGNED_MEM4K(rewrite, rewrite_size * ALIGNMENT); + ALIGNED_MEM4K(readback, whole_range_size * ALIGNMENT); + memset(left, 0x11, left_size * ALIGNMENT); + memset(right, 0x22, right_size * ALIGNMENT); + memset(rewrite, 0x33, rewrite_size * ALIGNMENT); + + auto file = create_file_rw(/*sparse = */ false, /*hybrid = */ true); + ASSERT_EQ((ssize_t)(left_size * ALIGNMENT), + file->pwrite(left, left_size * ALIGNMENT, left_start * ALIGNMENT)); + ASSERT_EQ((ssize_t)(right_size * ALIGNMENT), + file->pwrite(right, right_size * ALIGNMENT, right_start * ALIGNMENT)); + auto data_size = file_size(lfs, data_name.back().c_str()); + auto index_size = file_size(lfs, idx_name.back().c_str()); + + // Rewrite [32, 192): it reuses [32, 64) and [128, 192), and appends [64, 128). + ASSERT_EQ((ssize_t)(rewrite_size * ALIGNMENT), + file->pwrite(rewrite, rewrite_size * ALIGNMENT, rewrite_start * ALIGNMENT)); + EXPECT_EQ(data_size + 64 * ALIGNMENT, file_size(lfs, data_name.back().c_str())); + EXPECT_EQ(index_size + sizeof(SegmentMapping), file_size(lfs, idx_name.back().c_str())); + + ASSERT_EQ((ssize_t)(whole_range_size * ALIGNMENT), + file->pread(readback, whole_range_size * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(left, readback, rewrite_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(rewrite, readback + rewrite_start * ALIGNMENT, + rewrite_size * ALIGNMENT)); + EXPECT_EQ(0, memcmp(right + (rewrite_start + rewrite_size - right_start) * ALIGNMENT, + readback + (rewrite_start + rewrite_size) * ALIGNMENT, + (whole_range_size - rewrite_start - rewrite_size) * ALIGNMENT)); + delete file; + + auto reopened = open_file_rw(); + ASSERT_EQ((ssize_t)(whole_range_size * ALIGNMENT), + reopened->pread(readback, whole_range_size * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(left, readback, rewrite_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(rewrite, readback + rewrite_start * ALIGNMENT, + rewrite_size * ALIGNMENT)); + EXPECT_EQ(0, memcmp(right + (rewrite_start + rewrite_size - right_start) * ALIGNMENT, + readback + (rewrite_start + rewrite_size) * ALIGNMENT, + (whole_range_size - rewrite_start - rewrite_size) * ALIGNMENT)); + delete reopened; +} + +// Seed upper data [0,64), then discard [64,128); write [32,160), appending former zeroed blocks. +TEST_F(FileTest, hybrid_rw_appends_over_zeroed_upper_range) { + constexpr size_t data_size_in_blocks = 64; + constexpr size_t discard_start = 64; + constexpr size_t discard_size = 64; + constexpr size_t rewrite_start = 32; + constexpr size_t rewrite_size = 128; + constexpr size_t whole_range_size = 160; + ALIGNED_MEM4K(initial, data_size_in_blocks * ALIGNMENT); + ALIGNED_MEM4K(rewrite, rewrite_size * ALIGNMENT); + ALIGNED_MEM4K(readback, whole_range_size * ALIGNMENT); + memset(initial, 0x11, data_size_in_blocks * ALIGNMENT); + memset(rewrite, 0x22, rewrite_size * ALIGNMENT); + + auto file = create_file_rw(/*sparse = */ false, /*hybrid = */ true); + ASSERT_EQ((ssize_t)(data_size_in_blocks * ALIGNMENT), + file->pwrite(initial, data_size_in_blocks * ALIGNMENT, 0)); + ASSERT_EQ(0, file->fallocate(3, discard_start * ALIGNMENT, discard_size * ALIGNMENT)); + auto data_size = file_size(lfs, data_name.back().c_str()); + + // [32,64) rewrites data, [64,128) replaces zeroed mappings, and [128,160) appends a hole. + ASSERT_EQ((ssize_t)(rewrite_size * ALIGNMENT), + file->pwrite(rewrite, rewrite_size * ALIGNMENT, rewrite_start * ALIGNMENT)); + EXPECT_EQ(data_size + (discard_size + whole_range_size - (discard_start + discard_size)) * ALIGNMENT, + file_size(lfs, data_name.back().c_str())); + ASSERT_EQ((ssize_t)(whole_range_size * ALIGNMENT), + file->pread(readback, whole_range_size * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(initial, readback, rewrite_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(rewrite, readback + rewrite_start * ALIGNMENT, + rewrite_size * ALIGNMENT)); + delete file; + + auto reopened = open_file_rw(); + ASSERT_EQ((ssize_t)(whole_range_size * ALIGNMENT), + reopened->pread(readback, whole_range_size * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(initial, readback, rewrite_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(rewrite, readback + rewrite_start * ALIGNMENT, + rewrite_size * ALIGNMENT)); + delete reopened; +} + +// Stack lower [64,128) with upper [0,64); write [0,128), appending instead of overwriting lower. +TEST_F(FileTest, hybrid_rw_never_reuses_lower_range) { + constexpr size_t upper_size = 64; + constexpr size_t lower_start = 64; + constexpr size_t lower_size = 64; + constexpr size_t rewrite_size = 128; + ALIGNED_MEM4K(lower_data, lower_size * ALIGNMENT); + ALIGNED_MEM4K(upper_data, upper_size * ALIGNMENT); + ALIGNED_MEM4K(rewrite, rewrite_size * ALIGNMENT); + ALIGNED_MEM4K(readback, rewrite_size * ALIGNMENT); + memset(lower_data, 0x11, lower_size * ALIGNMENT); + memset(upper_data, 0x22, upper_size * ALIGNMENT); + memset(rewrite, 0x33, rewrite_size * ALIGNMENT); + + auto lower_rw = create_file_rw(); + ASSERT_EQ((ssize_t)(lower_size * ALIGNMENT), + lower_rw->pwrite(lower_data, lower_size * ALIGNMENT, lower_start * ALIGNMENT)); + IFileRO *lower = nullptr; + ASSERT_EQ(0, lower_rw->close_seal(&lower)); + delete lower_rw; + + auto upper = create_file_rw(/*sparse = */ false, /*hybrid = */ true); + auto file = stack_files(upper, lower, /*ownership = */ true, /*check_order = */ false); + ASSERT_NE(nullptr, file); + ASSERT_EQ((ssize_t)(upper_size * ALIGNMENT), file->pwrite(upper_data, upper_size * ALIGNMENT, 0)); + auto data_size = file_size(lfs, data_name.back().c_str()); + + ASSERT_EQ((ssize_t)(rewrite_size * ALIGNMENT), file->pwrite(rewrite, rewrite_size * ALIGNMENT, 0)); + EXPECT_EQ(data_size + lower_size * ALIGNMENT, file_size(lfs, data_name.back().c_str())); + ASSERT_EQ((ssize_t)(rewrite_size * ALIGNMENT), + file->pread(readback, rewrite_size * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(rewrite, readback, rewrite_size * ALIGNMENT)); + delete file; +} + +// Seed upper [0,64), [128,192), and [256,320); write [32,288), appending both interior holes. +TEST_F(FileTest, hybrid_rw_reuses_three_upper_ranges_across_two_holes) { + constexpr size_t range_size = 64; + constexpr size_t middle_start = 128; + constexpr size_t right_start = 256; + constexpr size_t rewrite_start = 32; + constexpr size_t rewrite_size = 256; + constexpr size_t whole_range_size = right_start + range_size; + ALIGNED_MEM4K(left, range_size * ALIGNMENT); + ALIGNED_MEM4K(middle, range_size * ALIGNMENT); + ALIGNED_MEM4K(right, range_size * ALIGNMENT); + ALIGNED_MEM4K(rewrite, rewrite_size * ALIGNMENT); + ALIGNED_MEM4K(readback, whole_range_size * ALIGNMENT); + memset(left, 0x11, range_size * ALIGNMENT); + memset(middle, 0x22, range_size * ALIGNMENT); + memset(right, 0x44, range_size * ALIGNMENT); + memset(rewrite, 0x33, rewrite_size * ALIGNMENT); + + auto file = create_file_rw(/*sparse = */ false, /*hybrid = */ true); + ASSERT_EQ((ssize_t)(range_size * ALIGNMENT), file->pwrite(left, range_size * ALIGNMENT, 0)); + ASSERT_EQ((ssize_t)(range_size * ALIGNMENT), + file->pwrite(middle, range_size * ALIGNMENT, middle_start * ALIGNMENT)); + ASSERT_EQ((ssize_t)(range_size * ALIGNMENT), + file->pwrite(right, range_size * ALIGNMENT, right_start * ALIGNMENT)); + auto data_size = file_size(lfs, data_name.back().c_str()); + + ASSERT_EQ((ssize_t)(rewrite_size * ALIGNMENT), + file->pwrite(rewrite, rewrite_size * ALIGNMENT, rewrite_start * ALIGNMENT)); + EXPECT_EQ(data_size + 2 * range_size * ALIGNMENT, file_size(lfs, data_name.back().c_str())); + ASSERT_EQ((ssize_t)(whole_range_size * ALIGNMENT), + file->pread(readback, whole_range_size * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(left, readback, rewrite_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(rewrite, readback + rewrite_start * ALIGNMENT, + rewrite_size * ALIGNMENT)); + EXPECT_EQ(0, memcmp(right + (rewrite_start + rewrite_size - right_start) * ALIGNMENT, + readback + (rewrite_start + rewrite_size) * ALIGNMENT, + (whole_range_size - rewrite_start - rewrite_size) * ALIGNMENT)); + delete file; + + auto reopened = open_file_rw(); + ASSERT_EQ((ssize_t)(whole_range_size * ALIGNMENT), + reopened->pread(readback, whole_range_size * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(left, readback, rewrite_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(rewrite, readback + rewrite_start * ALIGNMENT, + rewrite_size * ALIGNMENT)); + EXPECT_EQ(0, memcmp(right + (rewrite_start + rewrite_size - right_start) * ALIGNMENT, + readback + (rewrite_start + rewrite_size) * ALIGNMENT, + (whole_range_size - rewrite_start - rewrite_size) * ALIGNMENT)); + delete reopened; +} + +// Seed upper [32,96); rewrite exactly [32,96), requiring neither data nor index growth. +TEST_F(FileTest, hybrid_rw_reuses_exact_upper_range) { + constexpr size_t write_start = 32; + constexpr size_t write_size = 64; + ALIGNED_MEM4K(initial, write_size * ALIGNMENT); + ALIGNED_MEM4K(rewrite, write_size * ALIGNMENT); + ALIGNED_MEM4K(readback, write_size * ALIGNMENT); + memset(initial, 0x11, write_size * ALIGNMENT); + memset(rewrite, 0x22, write_size * ALIGNMENT); + + auto file = create_file_rw(/*sparse = */ false, /*hybrid = */ true); + ASSERT_EQ((ssize_t)(write_size * ALIGNMENT), + file->pwrite(initial, write_size * ALIGNMENT, write_start * ALIGNMENT)); + auto data_size = file_size(lfs, data_name.back().c_str()); + auto index_size = file_size(lfs, idx_name.back().c_str()); + + ASSERT_EQ((ssize_t)(write_size * ALIGNMENT), + file->pwrite(rewrite, write_size * ALIGNMENT, write_start * ALIGNMENT)); + EXPECT_EQ(data_size, file_size(lfs, data_name.back().c_str())); + EXPECT_EQ(index_size, file_size(lfs, idx_name.back().c_str())); + ASSERT_EQ((ssize_t)(write_size * ALIGNMENT), + file->pread(readback, write_size * ALIGNMENT, write_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(rewrite, readback, write_size * ALIGNMENT)); + delete file; + + auto reopened = open_file_rw(); + ASSERT_EQ((ssize_t)(write_size * ALIGNMENT), + reopened->pread(readback, write_size * ALIGNMENT, write_start * ALIGNMENT)); + EXPECT_EQ(0, memcmp(rewrite, readback, write_size * ALIGNMENT)); + delete reopened; +} + +// Seed upper [0,64) and [128,192); write the exact gap [64,128), without touching either neighbor. +TEST_F(FileTest, hybrid_rw_appends_exact_gap_between_upper_ranges) { + constexpr size_t range_size = 64; + constexpr size_t right_start = 128; + constexpr size_t whole_range_size = right_start + range_size; + ALIGNED_MEM4K(left, range_size * ALIGNMENT); + ALIGNED_MEM4K(right, range_size * ALIGNMENT); + ALIGNED_MEM4K(gap, range_size * ALIGNMENT); + ALIGNED_MEM4K(readback, whole_range_size * ALIGNMENT); + memset(left, 0x11, range_size * ALIGNMENT); + memset(right, 0x22, range_size * ALIGNMENT); + memset(gap, 0x33, range_size * ALIGNMENT); + + auto file = create_file_rw(/*sparse = */ false, /*hybrid = */ true); + ASSERT_EQ((ssize_t)(range_size * ALIGNMENT), file->pwrite(left, range_size * ALIGNMENT, 0)); + ASSERT_EQ((ssize_t)(range_size * ALIGNMENT), + file->pwrite(right, range_size * ALIGNMENT, right_start * ALIGNMENT)); + auto data_size = file_size(lfs, data_name.back().c_str()); + auto index_size = file_size(lfs, idx_name.back().c_str()); + + ASSERT_EQ((ssize_t)(range_size * ALIGNMENT), + file->pwrite(gap, range_size * ALIGNMENT, range_size * ALIGNMENT)); + EXPECT_EQ(data_size + range_size * ALIGNMENT, file_size(lfs, data_name.back().c_str())); + EXPECT_EQ(index_size + sizeof(SegmentMapping), file_size(lfs, idx_name.back().c_str())); + ASSERT_EQ((ssize_t)(whole_range_size * ALIGNMENT), + file->pread(readback, whole_range_size * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(left, readback, range_size * ALIGNMENT)); + EXPECT_EQ(0, memcmp(gap, readback + range_size * ALIGNMENT, range_size * ALIGNMENT)); + EXPECT_EQ(0, memcmp(right, readback + right_start * ALIGNMENT, range_size * ALIGNMENT)); + delete file; + + auto reopened = open_file_rw(); + ASSERT_EQ((ssize_t)(whole_range_size * ALIGNMENT), + reopened->pread(readback, whole_range_size * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(left, readback, range_size * ALIGNMENT)); + EXPECT_EQ(0, memcmp(gap, readback + range_size * ALIGNMENT, range_size * ALIGNMENT)); + EXPECT_EQ(0, memcmp(right, readback + right_start * ALIGNMENT, range_size * ALIGNMENT)); + delete reopened; +} + +// Stack a sealed lower with hybrid upper; seed upper [1,2), then write [0,2): append [0,1). +TEST_F(FileTest, hybrid_rw_reuses_stacked_upper_data) { + ALIGNED_MEM4K(initial, ALIGNMENT); + ALIGNED_MEM4K(rewrite, 2 * ALIGNMENT); + ALIGNED_MEM4K(readback, 2 * ALIGNMENT); + memset(initial, 0x11, ALIGNMENT); + memset(rewrite, 0x22, 2 * ALIGNMENT); + + auto lower_rw = create_file_rw(); + ASSERT_EQ((ssize_t)ALIGNMENT, lower_rw->pwrite(initial, ALIGNMENT, 3 * ALIGNMENT)); + IFileRO *lower = nullptr; + ASSERT_EQ(0, lower_rw->close_seal(&lower)); + delete lower_rw; + + auto upper = create_file_rw(/*sparse = */ false, /*hybrid = */ true); + auto file = stack_files(upper, lower, /*ownership = */ true, /*check_order = */ false); + ASSERT_NE(nullptr, file); + ASSERT_EQ((ssize_t)ALIGNMENT, file->pwrite(initial, ALIGNMENT, ALIGNMENT)); + auto data_size = file_size(lfs, data_name.back().c_str()); + + // After stacking, the live upper index is ComboIndex::mapping, not m_index0. + ASSERT_EQ((ssize_t)(2 * ALIGNMENT), file->pwrite(rewrite, 2 * ALIGNMENT, 0)); + EXPECT_EQ(data_size + ALIGNMENT, file_size(lfs, data_name.back().c_str())); + ASSERT_EQ((ssize_t)(2 * ALIGNMENT), file->pread(readback, 2 * ALIGNMENT, 0)); + EXPECT_EQ(0, memcmp(rewrite, readback, 2 * ALIGNMENT)); + delete file; +} + class FileTest1 : public FileTest { public: virtual void SetUp() override { @@ -666,8 +1037,6 @@ TEST_F(FileTest3, seek_data) { delete fmerged; delete[] data; } - - TEST_F(FileTest3, sparsefile_close_seal) { CleanUp(); cout << "generating " << FLAGS_layers << " RO layers by randwrite()" << endl; @@ -801,8 +1170,6 @@ TEST_F(FileTest3, restack) { verify_file(file); delete file; } - - TEST_F(FileTest3, restack_sparse) { CleanUp(); cout << "generating " << FLAGS_layers << " RO layers by randwrite()" << endl;