1
0
mirror of https://github.com/facebook/rocksdb.git synced 2026-10-23 08:09:02 +03:00
Files
rocksdb/utilities/copy_engine/copy_engine_test.cc
Ranjan Banerjee cda4a73a19 Fsync destination files in parallel CheckpointEngine copies (#15038)
Summary:
Parallel checkpoint copies were never fsynced. `CopyEngine::CopyOrCreateFile`
gated the destination sync on `use_fsync` (wired to `db_options.use_fsync`) --
but `use_fsync` means "fsync vs fdatasync", not "whether to sync". With the
default `use_fsync=false`, the parallel path skipped the sync, so a copied
MANIFEST/SST stayed in the unsynced buffer and was dropped by
`TestFSWritableFile::Close` under `sync_fault_injection`, leaving a 0-byte file
and checkpoint corruption:

  Corruption: no log_file_number, next_file_number, last_sequence entry in MANIFEST
  Corruption: Sst file size mismatch between expected N and file system 0

Serial `CopyFile` always syncs (using `use_fsync` only to pick the flavor), so
it never hit this.

Fix: split `WorkItem` / `CopyOrCreateFile` into `sync` (whether to sync) and
`use_fsync` (fsync vs fdatasync). CheckpointEngine now passes `sync=true,
use_fsync=db_options.use_fsync`, matching serial. BackupEngine is unchanged --
`sync=options_.sync` with the new `use_fsync=false` default reproduces the prior
behavior.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15038

Reviewed By: xingbowang

Differential Revision: D114414620

Pulled By: rban1

fbshipit-source-id: e0f1f3dbadcd48c5a5c2a11111a266dbed623f86
2026-08-03 10:38:26 -07:00

194 lines
5.5 KiB
C++

// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "utilities/copy_engine/copy_engine.h"
#include <future>
#include <string>
#include <vector>
#include "file/file_util.h"
#include "port/stack_trace.h"
#include "rocksdb/env.h"
#include "test_util/testharness.h"
namespace ROCKSDB_NAMESPACE {
class CopyEngineTest : public testing::Test {
protected:
CopyEngineTest() {
env_ = Env::Default();
dir_ = test::PerThreadDBPath(env_, "copy_engine_test");
}
// PerThreadDBPath is deterministic per (name, pid, thread), so every case in
// this fixture maps to the same directory. Destroy it around each case so
// files from one case (or a prior/repeated run) cannot leak into the next.
void SetUp() override {
ASSERT_OK(DestroyDir(env_, dir_));
ASSERT_OK(env_->CreateDirIfMissing(dir_));
}
void TearDown() override { ASSERT_OK(DestroyDir(env_, dir_)); }
std::string SrcPath(int i) const {
return dir_ + "/src_" + std::to_string(i);
}
std::string DstPath(int i) const {
return dir_ + "/dst_" + std::to_string(i);
}
void CreateSrc(int i, const std::string& content) {
ASSERT_OK(WriteStringToFile(env_, content, SrcPath(i),
/*should_sync=*/false));
}
// Varied sizes exercise the dynamic pull-based pool (not a static split).
std::string ContentFor(int i) const {
return "file-" + std::to_string(i) + "-" + std::string((i + 1) * 1024, 'x');
}
WorkItem MakeCopyItem(int i) {
return WorkItem(SrcPath(i), DstPath(i), Temperature::kUnknown,
Temperature::kUnknown, /*contents=*/"", env_, env_,
EnvOptions(), /*sync=*/false, /*use_fsync=*/false,
/*rate_limiter=*/nullptr,
/*size_limit=*/0, /*stats=*/nullptr);
}
WorkItem MakeLinkItem(int i) {
WorkItem w = MakeCopyItem(i);
w.type = WorkItemType::Link;
return w;
}
void VerifyDstMatches(int i, const std::string& expected) {
std::string got;
ASSERT_OK(ReadFileToString(env_, DstPath(i), &got));
ASSERT_EQ(expected, got);
}
Env* env_;
std::string dir_;
};
TEST_F(CopyEngineTest, ParallelCopy) {
constexpr int kNumFiles = 16;
std::vector<std::string> contents;
for (int i = 0; i < kNumFiles; ++i) {
contents.push_back(ContentFor(i));
CreateSrc(i, contents[i]);
}
CopyEngineOptions options;
options.max_background_operations = 4;
CopyEngine engine(std::move(options));
std::vector<std::future<WorkItemResult>> results;
for (int i = 0; i < kNumFiles; ++i) {
WorkItem w = MakeCopyItem(i);
results.push_back(w.result.get_future());
engine.Submit(std::move(w));
}
for (int i = 0; i < kNumFiles; ++i) {
WorkItemResult r = results[i].get();
ASSERT_OK(r.io_status);
ASSERT_EQ(contents[i].size(), r.size);
VerifyDstMatches(i, contents[i]);
}
}
TEST_F(CopyEngineTest, ParallelLink) {
constexpr int kNumFiles = 16;
std::vector<std::string> contents;
for (int i = 0; i < kNumFiles; ++i) {
contents.push_back(ContentFor(i));
CreateSrc(i, contents[i]);
}
CopyEngineOptions options;
options.max_background_operations = 4;
CopyEngine engine(std::move(options));
std::vector<std::future<WorkItemResult>> results;
for (int i = 0; i < kNumFiles; ++i) {
WorkItem w = MakeLinkItem(i);
results.push_back(w.result.get_future());
engine.Submit(std::move(w));
}
bool link_supported = true;
for (int i = 0; i < kNumFiles; ++i) {
WorkItemResult r = results[i].get();
if (r.io_status.IsNotSupported()) {
link_supported = false;
continue;
}
ASSERT_OK(r.io_status);
VerifyDstMatches(i, contents[i]);
}
if (!link_supported) {
ROCKSDB_GTEST_SKIP("FileSystem does not support hard links");
}
}
TEST_F(CopyEngineTest, SerialCopyMatchesSingleThread) {
constexpr int kNumFiles = 5;
std::vector<std::string> contents;
for (int i = 0; i < kNumFiles; ++i) {
contents.push_back(ContentFor(i));
CreateSrc(i, contents[i]);
}
CopyEngineOptions options;
options.max_background_operations = 1;
CopyEngine engine(std::move(options));
std::vector<std::future<WorkItemResult>> results;
for (int i = 0; i < kNumFiles; ++i) {
WorkItem w = MakeCopyItem(i);
results.push_back(w.result.get_future());
engine.Submit(std::move(w));
}
for (int i = 0; i < kNumFiles; ++i) {
WorkItemResult r = results[i].get();
ASSERT_OK(r.io_status);
VerifyDstMatches(i, contents[i]);
}
}
TEST_F(CopyEngineTest, MissingSourceReportsErrorPerItem) {
CreateSrc(0, ContentFor(0));
CopyEngineOptions options;
options.max_background_operations = 2;
CopyEngine engine(std::move(options));
WorkItem ok_item = MakeCopyItem(0);
auto ok_future = ok_item.result.get_future();
engine.Submit(std::move(ok_item));
WorkItem bad_item = MakeCopyItem(1); // no source file on disk
auto bad_future = bad_item.result.get_future();
engine.Submit(std::move(bad_item));
WorkItemResult ok_result = ok_future.get();
ASSERT_OK(ok_result.io_status);
VerifyDstMatches(0, ContentFor(0));
WorkItemResult bad_result = bad_future.get();
ASSERT_NOK(bad_result.io_status);
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}