mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 22:55:23 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b47c9eea1 | |||
| c8a70ed41a | |||
| 693714c46d | |||
| 3471f71506 | |||
| 9d7c0b198f | |||
| b4e7dae121 | |||
| 1645e4dcc3 | |||
| 8e0f495253 | |||
| 41fe221a06 | |||
| 405a35f8c3 | |||
| 8244f13448 | |||
| c8bae6e29c | |||
| 07a00828af | |||
| f80bac500e |
+16
@@ -1,4 +1,19 @@
|
||||
# Rocksdb Change Log
|
||||
## 7.3.2 (07/13/2022)
|
||||
### Bug Fixes
|
||||
* Avoid a crash if the IDENTITY file is accidentally truncated to empty. A new DB ID will be written and generated on Open.
|
||||
* Fixed a possible corruption for users of `manual_wal_flush` and/or `FlushWAL(true /* sync */)`, together with `track_and_verify_wals_in_manifest == true`. For those users, losing unsynced data (e.g., due to power loss) could make future DB opens fail with a `Status::Corruption` complaining about missing WAL data.
|
||||
|
||||
### Behavior Change
|
||||
* Add an extra sanity check in `GetSortedWalFiles()` (also used by `GetLiveFilesStorageInfo()`, `BackupEngine`, and `Checkpoint`) to reduce risk of successfully created backup or checkpoint failing to open because of missing WAL file.
|
||||
* For track_and_verify_wals_in_manifest, revert to the original behavior before #10087: syncing of live WAL file is not tracked, and we track only the synced sizes of **closed** WALs. (PR #10330).
|
||||
|
||||
## 7.3.1 (06/08/2022)
|
||||
### Bug Fixes
|
||||
* Fix a bug in WAL tracking. Before this PR (#10087), calling `SyncWAL()` on the only WAL file of the db will not log the event in MANIFEST, thus allowing a subsequent `DB::Open` even if the WAL file is missing or corrupted.
|
||||
* Fixed a bug for non-TransactionDB with avoid_flush_during_recovery = true and TransactionDB where in case of crash, min_log_number_to_keep may not change on recovery and persisting a new MANIFEST with advanced log_numbers for some column families, results in "column family inconsistency" error on second recovery. As a solution, RocksDB will persist the new MANIFEST after successfully syncing the new WAL. If a future recovery starts from the new MANIFEST, then it means the new WAL is successfully synced. Due to the sentinel empty write batch at the beginning, kPointInTimeRecovery of WAL is guaranteed to go after this point. If future recovery starts from the old MANIFEST, it means the writing the new MANIFEST failed. We won't have the "SST ahead of WAL" error.
|
||||
* Fixed a bug where RocksDB DB::Open() may creates and writes to two new MANIFEST files even before recovery succeeds. Now writes to MANIFEST are persisted only after recovery is successful.
|
||||
|
||||
## 7.3.0 (05/20/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where manual flush would block forever even though flush options had wait=false.
|
||||
@@ -14,6 +29,7 @@
|
||||
* RemoteCompaction supports table_properties_collector_factories override on compaction worker.
|
||||
* Start tracking SST unique id in MANIFEST, which will be used to verify with SST properties during DB open to make sure the SST file is not overwritten or misplaced. A db option `verify_sst_unique_id_in_manifest` is introduced to enable/disable the verification, if enabled all SST files will be opened during DB-open to verify the unique id (default is false), so it's recommended to use it with `max_open_files = -1` to pre-open the files.
|
||||
* Added the ability to concurrently read data blocks from multiple files in a level in batched MultiGet. This can be enabled by setting the async_io option in ReadOptions. Using this feature requires a FileSystem that supports ReadAsync (PosixFileSystem is not supported yet for this), and for RocksDB to be compiled with folly and c++20.
|
||||
* Add FileSystem::ReadAsync API in io_tracing.
|
||||
|
||||
### Public API changes
|
||||
* Add rollback_deletion_type_callback to TransactionDBOptions so that write-prepared transactions know whether to issue a Delete or SingleDelete to cancel a previous key written during prior prepare phase. The PR aims to prevent mixing SingleDeletes and Deletes for the same key that can lead to undefined behaviors for write-prepared transactions.
|
||||
|
||||
@@ -1876,7 +1876,7 @@ testutil_test: $(OBJ_DIR)/test_util/testutil_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
io_tracer_test: $(OBJ_DIR)/trace_replay/io_tracer_test.o $(OBJ_DIR)/trace_replay/io_tracer.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
prefetch_test: $(OBJ_DIR)/file/prefetch_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
prefetch_test: $(OBJ_DIR)/file/prefetch_test.o $(OBJ_DIR)/tools/io_tracer_parser_tool.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
io_tracer_parser_test: $(OBJ_DIR)/tools/io_tracer_parser_test.o $(OBJ_DIR)/tools/io_tracer_parser_tool.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
|
||||
+67
-10
@@ -1065,7 +1065,7 @@ INSTANTIATE_TEST_CASE_P(CorruptionTest, CrashDuringRecoveryWithCorruptionTest,
|
||||
// The combination of corrupting a WAL and injecting an error during subsequent
|
||||
// re-open exposes the bug of prematurely persisting a new MANIFEST with
|
||||
// advanced ColumnFamilyData::log_number.
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, DISABLED_CrashDuringRecovery) {
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, CrashDuringRecovery) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.track_and_verify_wals_in_manifest =
|
||||
@@ -1107,7 +1107,8 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest, DISABLED_CrashDuringRecovery) {
|
||||
// number. TEST_SwitchMemtable makes sure WALs are not synced and test can
|
||||
// corrupt un-sync WAL.
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i), "value"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i),
|
||||
"value" + std::to_string(i)));
|
||||
ASSERT_OK(dbimpl->TEST_SwitchMemtable());
|
||||
}
|
||||
|
||||
@@ -1188,6 +1189,23 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest, DISABLED_CrashDuringRecovery) {
|
||||
{
|
||||
options.avoid_flush_during_recovery = avoid_flush_during_recovery_;
|
||||
ASSERT_OK(DB::Open(options, dbname_, cf_descs, &handles, &db_));
|
||||
|
||||
// Verify that data is not lost.
|
||||
{
|
||||
std::string v;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), handles[1], "old_key", &v));
|
||||
ASSERT_EQ("dontcare", v);
|
||||
|
||||
v.clear();
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "key" + std::to_string(0), &v));
|
||||
ASSERT_EQ("value" + std::to_string(0), v);
|
||||
|
||||
// Since it's corrupting second last wal, below key is not found.
|
||||
v.clear();
|
||||
ASSERT_EQ(db_->Get(ReadOptions(), "key" + std::to_string(1), &v),
|
||||
Status::NotFound());
|
||||
}
|
||||
|
||||
for (auto* h : handles) {
|
||||
delete h;
|
||||
}
|
||||
@@ -1219,8 +1237,7 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest, DISABLED_CrashDuringRecovery) {
|
||||
// The combination of corrupting a WAL and injecting an error during subsequent
|
||||
// re-open exposes the bug of prematurely persisting a new MANIFEST with
|
||||
// advanced ColumnFamilyData::log_number.
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
DISABLED_TxnDbCrashDuringRecovery) {
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, TxnDbCrashDuringRecovery) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
@@ -1271,13 +1288,14 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
|
||||
// Put and flush cf0
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_OK(txn_db->Put(WriteOptions(), "dontcare", "value"));
|
||||
ASSERT_OK(txn_db->Put(WriteOptions(), "key" + std::to_string(i),
|
||||
"value" + std::to_string(i)));
|
||||
ASSERT_OK(dbimpl->TEST_SwitchMemtable());
|
||||
}
|
||||
|
||||
// Put cf1
|
||||
txn = txn_db->BeginTransaction(WriteOptions(), TransactionOptions());
|
||||
ASSERT_OK(txn->Put(handles[1], "foo1", "value"));
|
||||
ASSERT_OK(txn->Put(handles[1], "foo1", "value1"));
|
||||
ASSERT_OK(txn->Commit());
|
||||
|
||||
delete txn;
|
||||
@@ -1337,7 +1355,6 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
std::vector<uint64_t> file_nums;
|
||||
GetSortedWalFiles(file_nums);
|
||||
size_t size = file_nums.size();
|
||||
assert(size >= 2);
|
||||
uint64_t log_num = file_nums[size - 1];
|
||||
CorruptFileWithTruncation(FileType::kWalFile, log_num);
|
||||
}
|
||||
@@ -1354,6 +1371,27 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
{
|
||||
ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, cf_descs,
|
||||
&handles, &txn_db));
|
||||
|
||||
// Verify that data is not lost.
|
||||
{
|
||||
std::string v;
|
||||
// Key not visible since it's not committed.
|
||||
ASSERT_EQ(txn_db->Get(ReadOptions(), handles[1], "foo", &v),
|
||||
Status::NotFound());
|
||||
|
||||
v.clear();
|
||||
ASSERT_OK(txn_db->Get(ReadOptions(), "key" + std::to_string(0), &v));
|
||||
ASSERT_EQ("value" + std::to_string(0), v);
|
||||
|
||||
// Last WAL is corrupted which contains two keys below.
|
||||
v.clear();
|
||||
ASSERT_EQ(txn_db->Get(ReadOptions(), "key" + std::to_string(1), &v),
|
||||
Status::NotFound());
|
||||
v.clear();
|
||||
ASSERT_EQ(txn_db->Get(ReadOptions(), handles[1], "foo1", &v),
|
||||
Status::NotFound());
|
||||
}
|
||||
|
||||
for (auto* h : handles) {
|
||||
delete h;
|
||||
}
|
||||
@@ -1396,8 +1434,7 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
// The combination of corrupting a WAL and injecting an error during subsequent
|
||||
// re-open exposes the bug of prematurely persisting a new MANIFEST with
|
||||
// advanced ColumnFamilyData::log_number.
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
DISABLED_CrashDuringRecoveryWithFlush) {
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, CrashDuringRecoveryWithFlush) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
@@ -1430,7 +1467,8 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
// Write to default_cf and flush this cf several times to advance wal
|
||||
// number.
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i), "value"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i),
|
||||
"value" + std::to_string(i)));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
}
|
||||
|
||||
@@ -1483,6 +1521,25 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest,
|
||||
{
|
||||
options.avoid_flush_during_recovery = avoid_flush_during_recovery_;
|
||||
ASSERT_OK(DB::Open(options, dbname_, cf_descs, &handles, &db_));
|
||||
|
||||
// Verify that data is not lost.
|
||||
{
|
||||
std::string v;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), handles[1], "old_key", &v));
|
||||
ASSERT_EQ("dontcare", v);
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
v.clear();
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "key" + std::to_string(i), &v));
|
||||
ASSERT_EQ("value" + std::to_string(i), v);
|
||||
}
|
||||
|
||||
// Since it's corrupting last wal after Flush, below key is not found.
|
||||
v.clear();
|
||||
ASSERT_EQ(db_->Get(ReadOptions(), handles[1], "dontcare", &v),
|
||||
Status::NotFound());
|
||||
}
|
||||
|
||||
for (auto* h : handles) {
|
||||
delete h;
|
||||
}
|
||||
|
||||
+94
-42
@@ -578,55 +578,84 @@ TEST_F(DBBasicTest, ManifestRollOver) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, IdentityAcrossRestarts1) {
|
||||
TEST_F(DBBasicTest, IdentityAcrossRestarts) {
|
||||
constexpr size_t kMinIdSize = 10;
|
||||
do {
|
||||
std::string id1;
|
||||
ASSERT_OK(db_->GetDbIdentity(id1));
|
||||
for (bool with_manifest : {false, true}) {
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
std::string id1, tmp;
|
||||
ASSERT_OK(db_->GetDbIdentity(id1));
|
||||
ASSERT_GE(id1.size(), kMinIdSize);
|
||||
|
||||
Options options = CurrentOptions();
|
||||
Reopen(options);
|
||||
std::string id2;
|
||||
ASSERT_OK(db_->GetDbIdentity(id2));
|
||||
// id1 should match id2 because identity was not regenerated
|
||||
ASSERT_EQ(id1.compare(id2), 0);
|
||||
Options options = CurrentOptions();
|
||||
options.write_dbid_to_manifest = with_manifest;
|
||||
Reopen(options);
|
||||
std::string id2;
|
||||
ASSERT_OK(db_->GetDbIdentity(id2));
|
||||
// id2 should match id1 because identity was not regenerated
|
||||
ASSERT_EQ(id1, id2);
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id2);
|
||||
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
ASSERT_OK(env_->DeleteFile(idfilename));
|
||||
Reopen(options);
|
||||
std::string id3;
|
||||
ASSERT_OK(db_->GetDbIdentity(id3));
|
||||
if (options.write_dbid_to_manifest) {
|
||||
ASSERT_EQ(id1.compare(id3), 0);
|
||||
} else {
|
||||
// id1 should NOT match id3 because identity was regenerated
|
||||
ASSERT_NE(id1.compare(id3), 0);
|
||||
// Recover from deleted/missing IDENTITY
|
||||
ASSERT_OK(env_->DeleteFile(idfilename));
|
||||
Reopen(options);
|
||||
std::string id3;
|
||||
ASSERT_OK(db_->GetDbIdentity(id3));
|
||||
if (with_manifest) {
|
||||
// id3 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id3);
|
||||
} else {
|
||||
// id3 should NOT match id1 because identity was regenerated
|
||||
ASSERT_NE(id1, id3);
|
||||
ASSERT_GE(id3.size(), kMinIdSize);
|
||||
}
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id3);
|
||||
|
||||
// Recover from truncated IDENTITY
|
||||
{
|
||||
std::unique_ptr<WritableFile> w;
|
||||
ASSERT_OK(env_->NewWritableFile(idfilename, &w, EnvOptions()));
|
||||
ASSERT_OK(w->Close());
|
||||
}
|
||||
Reopen(options);
|
||||
std::string id4;
|
||||
ASSERT_OK(db_->GetDbIdentity(id4));
|
||||
if (with_manifest) {
|
||||
// id4 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id4);
|
||||
} else {
|
||||
// id4 should NOT match id1 because identity was regenerated
|
||||
ASSERT_NE(id1, id4);
|
||||
ASSERT_GE(id4.size(), kMinIdSize);
|
||||
}
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id4);
|
||||
|
||||
// Recover from overwritten IDENTITY
|
||||
std::string silly_id = "asdf123456789";
|
||||
{
|
||||
std::unique_ptr<WritableFile> w;
|
||||
ASSERT_OK(env_->NewWritableFile(idfilename, &w, EnvOptions()));
|
||||
ASSERT_OK(w->Append(silly_id));
|
||||
ASSERT_OK(w->Close());
|
||||
}
|
||||
Reopen(options);
|
||||
std::string id5;
|
||||
ASSERT_OK(db_->GetDbIdentity(id5));
|
||||
if (with_manifest) {
|
||||
// id4 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id5);
|
||||
} else {
|
||||
ASSERT_EQ(id5, silly_id);
|
||||
}
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id5);
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, IdentityAcrossRestarts2) {
|
||||
do {
|
||||
std::string id1;
|
||||
ASSERT_OK(db_->GetDbIdentity(id1));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.write_dbid_to_manifest = true;
|
||||
Reopen(options);
|
||||
std::string id2;
|
||||
ASSERT_OK(db_->GetDbIdentity(id2));
|
||||
// id1 should match id2 because identity was not regenerated
|
||||
ASSERT_EQ(id1.compare(id2), 0);
|
||||
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
ASSERT_OK(env_->DeleteFile(idfilename));
|
||||
Reopen(options);
|
||||
std::string id3;
|
||||
ASSERT_OK(db_->GetDbIdentity(id3));
|
||||
// id1 should NOT match id3 because identity was regenerated
|
||||
ASSERT_EQ(id1, id3);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBasicTest, Snapshot) {
|
||||
env_->SetMockSleep();
|
||||
@@ -4096,6 +4125,29 @@ TEST_F(DBBasicTest, VerifyFileChecksums) {
|
||||
Reopen(options);
|
||||
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsInvalidArgument());
|
||||
}
|
||||
|
||||
// TODO: re-enable after we provide finer-grained control for WAL tracking to
|
||||
// meet the needs of different use cases, durability levels and recovery modes.
|
||||
TEST_F(DBBasicTest, DISABLED_ManualWalSync) {
|
||||
Options options = CurrentOptions();
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("x", "y"));
|
||||
// This does not create a new WAL.
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
EXPECT_FALSE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
|
||||
std::unique_ptr<LogFile> wal;
|
||||
Status s = db_->GetCurrentWalFile(&wal);
|
||||
ASSERT_OK(s);
|
||||
Close();
|
||||
|
||||
EXPECT_OK(env_->DeleteFile(LogFileName(dbname_, wal->LogNumber())));
|
||||
|
||||
ASSERT_TRUE(TryReopen(options).IsCorruption());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// A test class for intercepting random reads and injecting artificial
|
||||
|
||||
+40
-1
@@ -124,6 +124,9 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
}
|
||||
|
||||
Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
// Record tracked WALs as a (minimum) cross-check for directory scan
|
||||
std::vector<uint64_t> required_by_manifest;
|
||||
|
||||
// If caller disabled deletions, this function should return files that are
|
||||
// guaranteed not to be deleted until deletions are re-enabled. We need to
|
||||
// wait for pending purges to finish since WalManager doesn't know which
|
||||
@@ -137,6 +140,13 @@ Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
while (pending_purge_obsolete_files_ > 0 || bg_purge_scheduled_ > 0) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
|
||||
// Record tracked WALs as a (minimum) cross-check for directory scan
|
||||
const auto& manifest_wals = versions_->GetWalSet().GetWals();
|
||||
required_by_manifest.reserve(manifest_wals.size());
|
||||
for (const auto& wal : manifest_wals) {
|
||||
required_by_manifest.push_back(wal.first);
|
||||
}
|
||||
}
|
||||
|
||||
Status s = wal_manager_.GetSortedWalFiles(files);
|
||||
@@ -150,6 +160,29 @@ Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
assert(deletions_disabled.IsNotSupported());
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// Verify includes those required by manifest (one sorted list is superset
|
||||
// of the other)
|
||||
auto required = required_by_manifest.begin();
|
||||
auto included = files.begin();
|
||||
|
||||
while (required != required_by_manifest.end()) {
|
||||
if (included == files.end() || *required < (*included)->LogNumber()) {
|
||||
// FAIL - did not find
|
||||
return Status::Corruption(
|
||||
"WAL file " + std::to_string(*required) +
|
||||
" required by manifest but not in directory list");
|
||||
}
|
||||
if (*required == (*included)->LogNumber()) {
|
||||
++required;
|
||||
++included;
|
||||
} else {
|
||||
assert(*required > (*included)->LogNumber());
|
||||
++included;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -349,7 +382,13 @@ Status DBImpl::GetLiveFilesStorageInfo(
|
||||
TEST_SYNC_POINT("CheckpointImpl::CreateCheckpoint:SavedLiveFiles2");
|
||||
|
||||
if (s.ok()) {
|
||||
s = FlushWAL(false /* sync */);
|
||||
// To maximize the effectiveness of track_and_verify_wals_in_manifest,
|
||||
// sync WAL when it is enabled.
|
||||
s = FlushWAL(
|
||||
immutable_db_options_.track_and_verify_wals_in_manifest /* sync */);
|
||||
if (s.IsNotSupported()) { // read-only DB or similar
|
||||
s = Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("CheckpointImpl::CreateCustomCheckpoint:AfterGetLive1");
|
||||
|
||||
+10
-11
@@ -1337,6 +1337,7 @@ Status DBImpl::FlushWAL(bool sync) {
|
||||
}
|
||||
|
||||
Status DBImpl::SyncWAL() {
|
||||
TEST_SYNC_POINT("DBImpl::SyncWAL:Begin");
|
||||
autovector<log::Writer*, 1> logs_to_sync;
|
||||
bool need_log_dir_sync;
|
||||
uint64_t current_log_number;
|
||||
@@ -1349,7 +1350,7 @@ Status DBImpl::SyncWAL() {
|
||||
current_log_number = logfile_number_;
|
||||
|
||||
while (logs_.front().number <= current_log_number &&
|
||||
logs_.front().getting_synced) {
|
||||
logs_.front().IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
}
|
||||
// First check that logs are safe to sync in background.
|
||||
@@ -1366,8 +1367,7 @@ Status DBImpl::SyncWAL() {
|
||||
for (auto it = logs_.begin();
|
||||
it != logs_.end() && it->number <= current_log_number; ++it) {
|
||||
auto& log = *it;
|
||||
assert(!log.getting_synced);
|
||||
log.getting_synced = true;
|
||||
log.PrepareForSync();
|
||||
logs_to_sync.push_back(log.writer);
|
||||
}
|
||||
|
||||
@@ -1440,24 +1440,24 @@ Status DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir) {
|
||||
VersionEdit synced_wals;
|
||||
for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;) {
|
||||
auto& wal = *it;
|
||||
assert(wal.getting_synced);
|
||||
assert(wal.IsSyncing());
|
||||
|
||||
if (logs_.size() > 1) {
|
||||
if (immutable_db_options_.track_and_verify_wals_in_manifest &&
|
||||
wal.writer->file()->GetFileSize() > 0) {
|
||||
synced_wals.AddWal(wal.number,
|
||||
WalMetadata(wal.writer->file()->GetFileSize()));
|
||||
wal.GetPreSyncSize() > 0) {
|
||||
synced_wals.AddWal(wal.number, WalMetadata(wal.GetPreSyncSize()));
|
||||
}
|
||||
logs_to_free_.push_back(wal.ReleaseWriter());
|
||||
// To modify logs_ both mutex_ and log_write_mutex_ must be held
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
it = logs_.erase(it);
|
||||
} else {
|
||||
wal.getting_synced = false;
|
||||
wal.FinishSync();
|
||||
++it;
|
||||
}
|
||||
}
|
||||
assert(logs_.empty() || logs_[0].number > up_to ||
|
||||
(logs_.size() == 1 && !logs_[0].getting_synced));
|
||||
(logs_.size() == 1 && !logs_[0].IsSyncing()));
|
||||
|
||||
Status s;
|
||||
if (synced_wals.IsWalAddition()) {
|
||||
@@ -1477,8 +1477,7 @@ void DBImpl::MarkLogsNotSynced(uint64_t up_to) {
|
||||
for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;
|
||||
++it) {
|
||||
auto& wal = *it;
|
||||
assert(wal.getting_synced);
|
||||
wal.getting_synced = false;
|
||||
wal.FinishSync();
|
||||
}
|
||||
log_sync_cv_.SignalAll();
|
||||
}
|
||||
|
||||
+80
-6
@@ -1240,6 +1240,39 @@ class DBImpl : public DB {
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
|
||||
// RecoveryContext struct stores the context about version edits along
|
||||
// with corresponding column_family_data and column_family_options.
|
||||
class RecoveryContext {
|
||||
public:
|
||||
~RecoveryContext() {
|
||||
for (auto& edit_list : edit_lists_) {
|
||||
for (auto* edit : edit_list) {
|
||||
delete edit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateVersionEdits(ColumnFamilyData* cfd, const VersionEdit& edit) {
|
||||
assert(cfd != nullptr);
|
||||
if (map_.find(cfd->GetID()) == map_.end()) {
|
||||
uint32_t size = static_cast<uint32_t>(map_.size());
|
||||
map_.emplace(cfd->GetID(), size);
|
||||
cfds_.emplace_back(cfd);
|
||||
mutable_cf_opts_.emplace_back(cfd->GetLatestMutableCFOptions());
|
||||
edit_lists_.emplace_back(autovector<VersionEdit*>());
|
||||
}
|
||||
uint32_t i = map_[cfd->GetID()];
|
||||
edit_lists_[i].emplace_back(new VersionEdit(edit));
|
||||
}
|
||||
|
||||
std::unordered_map<uint32_t, uint32_t> map_; // cf_id to index;
|
||||
autovector<ColumnFamilyData*> cfds_;
|
||||
autovector<const MutableCFOptions*> mutable_cf_opts_;
|
||||
autovector<autovector<VersionEdit*>> edit_lists_;
|
||||
// files_to_delete_ contains sst files
|
||||
std::unordered_set<std::string> files_to_delete_;
|
||||
};
|
||||
|
||||
// Except in DB::Open(), WriteOptionsFile can only be called when:
|
||||
// Persist options to options file.
|
||||
// If need_mutex_lock = false, the method will lock DB mutex.
|
||||
@@ -1356,16 +1389,19 @@ class DBImpl : public DB {
|
||||
// be made to the descriptor are added to *edit.
|
||||
// recovered_seq is set to less than kMaxSequenceNumber if the log's tail is
|
||||
// skipped.
|
||||
// recovery_ctx stores the context about version edits and all those
|
||||
// edits are persisted to new Manifest after successfully syncing the new WAL.
|
||||
virtual Status Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, bool error_if_wal_file_exists = false,
|
||||
bool error_if_data_exists_in_wals = false,
|
||||
uint64_t* recovered_seq = nullptr);
|
||||
uint64_t* recovered_seq = nullptr,
|
||||
RecoveryContext* recovery_ctx = nullptr);
|
||||
|
||||
virtual bool OwnTablesAndLogs() const { return true; }
|
||||
|
||||
// Set DB identity file, and write DB ID to manifest if necessary.
|
||||
Status SetDBId(bool read_only);
|
||||
Status SetDBId(bool read_only, RecoveryContext* recovery_ctx);
|
||||
|
||||
// REQUIRES: db mutex held when calling this function, but the db mutex can
|
||||
// be released and re-acquired. Db mutex will be held when the function
|
||||
@@ -1374,12 +1410,15 @@ class DBImpl : public DB {
|
||||
// not referenced in the MANIFEST (e.g.
|
||||
// 1. It's best effort recovery;
|
||||
// 2. The VersionEdits referencing the SST files are appended to
|
||||
// MANIFEST, DB crashes when syncing the MANIFEST, the VersionEdits are
|
||||
// RecoveryContext, DB crashes when syncing the MANIFEST, the VersionEdits are
|
||||
// still not synced to MANIFEST during recovery.)
|
||||
// We delete these SST files. In the
|
||||
// It stores the SST files to be deleted in RecoveryContext. In the
|
||||
// meantime, we find out the largest file number present in the paths, and
|
||||
// bump up the version set's next_file_number_ to be 1 + largest_file_number.
|
||||
Status DeleteUnreferencedSstFiles();
|
||||
// recovery_ctx stores the context about version edits and files to be
|
||||
// deleted. All those edits are persisted to new Manifest after successfully
|
||||
// syncing the new WAL.
|
||||
Status DeleteUnreferencedSstFiles(RecoveryContext* recovery_ctx);
|
||||
|
||||
// SetDbSessionId() should be called in the constuctor DBImpl()
|
||||
// to ensure that db_session_id_ gets updated every time the DB is opened
|
||||
@@ -1389,6 +1428,14 @@ class DBImpl : public DB {
|
||||
Status FailIfTsSizesMismatch(const ColumnFamilyHandle* column_family,
|
||||
const Slice& ts) const;
|
||||
|
||||
// recovery_ctx stores the context about version edits and
|
||||
// LogAndApplyForRecovery persist all those edits to new Manifest after
|
||||
// successfully syncing new WAL.
|
||||
// LogAndApplyForRecovery should be called only once during recovery and it
|
||||
// should be called when RocksDB writes to a first new MANIFEST since this
|
||||
// recovery.
|
||||
Status LogAndApplyForRecovery(const RecoveryContext& recovery_ctx);
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class ErrorHandler;
|
||||
@@ -1465,12 +1512,38 @@ class DBImpl : public DB {
|
||||
return s;
|
||||
}
|
||||
|
||||
bool IsSyncing() { return getting_synced; }
|
||||
|
||||
uint64_t GetPreSyncSize() {
|
||||
assert(getting_synced);
|
||||
return pre_sync_size;
|
||||
}
|
||||
|
||||
void PrepareForSync() {
|
||||
assert(!getting_synced);
|
||||
// Size is expected to be monotonically increasing.
|
||||
assert(writer->file()->GetFlushedSize() >= pre_sync_size);
|
||||
getting_synced = true;
|
||||
pre_sync_size = writer->file()->GetFlushedSize();
|
||||
}
|
||||
|
||||
void FinishSync() {
|
||||
assert(getting_synced);
|
||||
getting_synced = false;
|
||||
}
|
||||
|
||||
uint64_t number;
|
||||
// Visual Studio doesn't support deque's member to be noncopyable because
|
||||
// of a std::unique_ptr as a member.
|
||||
log::Writer* writer; // own
|
||||
|
||||
private:
|
||||
// true for some prefix of logs_
|
||||
bool getting_synced = false;
|
||||
// The size of the file before the sync happens. This amount is guaranteed
|
||||
// to be persisted even if appends happen during sync so it can be used for
|
||||
// tracking the synced size in MANIFEST.
|
||||
uint64_t pre_sync_size = 0;
|
||||
};
|
||||
|
||||
// PurgeFileInfo is a structure to hold information of files to be deleted in
|
||||
@@ -1645,7 +1718,8 @@ class DBImpl : public DB {
|
||||
// corrupted_log_found is set to true if we recover from a corrupted log file.
|
||||
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only,
|
||||
bool* corrupted_log_found);
|
||||
bool* corrupted_log_found,
|
||||
RecoveryContext* recovery_ctx);
|
||||
|
||||
// The following two methods are used to flush a memtable to
|
||||
// storage. The first one is used at database RecoveryTime (when the
|
||||
|
||||
@@ -88,14 +88,13 @@ IOStatus DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
autovector<log::Writer*, 1> logs_to_sync;
|
||||
uint64_t current_log_number = logfile_number_;
|
||||
while (logs_.front().number < current_log_number &&
|
||||
logs_.front().getting_synced) {
|
||||
logs_.front().IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
}
|
||||
for (auto it = logs_.begin();
|
||||
it != logs_.end() && it->number < current_log_number; ++it) {
|
||||
auto& log = *it;
|
||||
assert(!log.getting_synced);
|
||||
log.getting_synced = true;
|
||||
log.PrepareForSync();
|
||||
logs_to_sync.push_back(log.writer);
|
||||
}
|
||||
|
||||
|
||||
+27
-26
@@ -280,7 +280,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
}
|
||||
while (!logs_.empty() && logs_.front().number < min_log_number) {
|
||||
auto& log = logs_.front();
|
||||
if (log.getting_synced) {
|
||||
if (log.IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
// logs_ could have changed while we were waiting.
|
||||
continue;
|
||||
@@ -863,7 +863,7 @@ uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
return min_log_number_to_keep;
|
||||
}
|
||||
|
||||
Status DBImpl::SetDBId(bool read_only) {
|
||||
Status DBImpl::SetDBId(bool read_only, RecoveryContext* recovery_ctx) {
|
||||
Status s;
|
||||
// Happens when immutable_db_options_.write_dbid_to_manifest is set to true
|
||||
// the very first time.
|
||||
@@ -889,15 +889,30 @@ Status DBImpl::SetDBId(bool read_only) {
|
||||
return s;
|
||||
}
|
||||
s = GetDbIdentityFromIdentityFile(&db_id_);
|
||||
if (s.ok() && db_id_.empty()) {
|
||||
// Patch version of same fix as
|
||||
// https://github.com/facebook/rocksdb/pull/10173
|
||||
// (less intrusive, more obviously low-touch)
|
||||
if (read_only) {
|
||||
db_id_ = env_->GenerateUniqueId();
|
||||
return Status::OK();
|
||||
} else {
|
||||
s = SetIdentityFile(env_, dbname_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
s = GetDbIdentityFromIdentityFile(&db_id_);
|
||||
}
|
||||
if (immutable_db_options_.write_dbid_to_manifest && s.ok()) {
|
||||
assert(!read_only);
|
||||
assert(recovery_ctx != nullptr);
|
||||
assert(versions_->GetColumnFamilySet() != nullptr);
|
||||
VersionEdit edit;
|
||||
edit.SetDBId(db_id_);
|
||||
Options options;
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
versions_->db_id_ = db_id_;
|
||||
s = versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(),
|
||||
mutable_cf_options, &edit, &mutex_, nullptr,
|
||||
/* new_descriptor_log */ false);
|
||||
recovery_ctx->UpdateVersionEdits(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), edit);
|
||||
}
|
||||
} else if (!read_only) {
|
||||
s = SetIdentityFile(env_, dbname_, db_id_);
|
||||
@@ -905,7 +920,7 @@ Status DBImpl::SetDBId(bool read_only) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
Status DBImpl::DeleteUnreferencedSstFiles(RecoveryContext* recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
std::vector<std::string> paths;
|
||||
paths.push_back(NormalizePath(dbname_ + std::string(1, kFilePathSeparator)));
|
||||
@@ -925,7 +940,6 @@ Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
|
||||
uint64_t next_file_number = versions_->current_next_file_number();
|
||||
uint64_t largest_file_number = next_file_number;
|
||||
std::set<std::string> files_to_delete;
|
||||
Status s;
|
||||
for (const auto& path : paths) {
|
||||
std::vector<std::string> files;
|
||||
@@ -943,8 +957,9 @@ Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
const std::string normalized_fpath = path + fname;
|
||||
largest_file_number = std::max(largest_file_number, number);
|
||||
if (type == kTableFile && number >= next_file_number &&
|
||||
files_to_delete.find(normalized_fpath) == files_to_delete.end()) {
|
||||
files_to_delete.insert(normalized_fpath);
|
||||
recovery_ctx->files_to_delete_.find(normalized_fpath) ==
|
||||
recovery_ctx->files_to_delete_.end()) {
|
||||
recovery_ctx->files_to_delete_.emplace(normalized_fpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -961,21 +976,7 @@ Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
assert(versions_->GetColumnFamilySet());
|
||||
ColumnFamilyData* default_cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
assert(default_cfd);
|
||||
s = versions_->LogAndApply(
|
||||
default_cfd, *default_cfd->GetLatestMutableCFOptions(), &edit, &mutex_,
|
||||
directories_.GetDbDir(), /*new_descriptor_log*/ false);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
mutex_.Unlock();
|
||||
for (const auto& fname : files_to_delete) {
|
||||
s = env_->DeleteFile(fname);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
recovery_ctx->UpdateVersionEdits(default_cfd, edit);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+92
-65
@@ -399,7 +399,7 @@ IOStatus Directories::SetDirectories(FileSystem* fs, const std::string& dbname,
|
||||
Status DBImpl::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
|
||||
bool error_if_wal_file_exists, bool error_if_data_exists_in_wals,
|
||||
uint64_t* recovered_seq) {
|
||||
uint64_t* recovered_seq, RecoveryContext* recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
bool is_new_db = false;
|
||||
@@ -524,9 +524,9 @@ Status DBImpl::Recover(
|
||||
return s;
|
||||
}
|
||||
}
|
||||
s = SetDBId(read_only);
|
||||
s = SetDBId(read_only, recovery_ctx);
|
||||
if (s.ok() && !read_only) {
|
||||
s = DeleteUnreferencedSstFiles();
|
||||
s = DeleteUnreferencedSstFiles(recovery_ctx);
|
||||
}
|
||||
|
||||
if (immutable_db_options_.paranoid_checks && s.ok()) {
|
||||
@@ -541,10 +541,6 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
}
|
||||
// DB mutex is already held
|
||||
if (s.ok() && immutable_db_options_.persist_stats_to_disk) {
|
||||
s = InitPersistStatsColumnFamily();
|
||||
}
|
||||
|
||||
std::vector<std::string> files_in_wal_dir;
|
||||
if (s.ok()) {
|
||||
@@ -614,7 +610,10 @@ Status DBImpl::Recover(
|
||||
WalNumber max_wal_number =
|
||||
versions_->GetWalSet().GetWals().rbegin()->first;
|
||||
edit.DeleteWalsBefore(max_wal_number + 1);
|
||||
s = versions_->LogAndApplyToDefaultColumnFamily(&edit, &mutex_);
|
||||
assert(recovery_ctx != nullptr);
|
||||
assert(versions_->GetColumnFamilySet() != nullptr);
|
||||
recovery_ctx->UpdateVersionEdits(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), edit);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -650,8 +649,8 @@ Status DBImpl::Recover(
|
||||
std::sort(wals.begin(), wals.end());
|
||||
|
||||
bool corrupted_wal_found = false;
|
||||
s = RecoverLogFiles(wals, &next_sequence, read_only,
|
||||
&corrupted_wal_found);
|
||||
s = RecoverLogFiles(wals, &next_sequence, read_only, &corrupted_wal_found,
|
||||
recovery_ctx);
|
||||
if (corrupted_wal_found && recovered_seq != nullptr) {
|
||||
*recovered_seq = next_sequence;
|
||||
}
|
||||
@@ -830,10 +829,30 @@ Status DBImpl::InitPersistStatsColumnFamily() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::LogAndApplyForRecovery(const RecoveryContext& recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
assert(versions_->descriptor_log_ == nullptr);
|
||||
Status s = versions_->LogAndApply(
|
||||
recovery_ctx.cfds_, recovery_ctx.mutable_cf_opts_,
|
||||
recovery_ctx.edit_lists_, &mutex_, directories_.GetDbDir());
|
||||
if (s.ok() && !(recovery_ctx.files_to_delete_.empty())) {
|
||||
mutex_.Unlock();
|
||||
for (const auto& fname : recovery_ctx.files_to_delete_) {
|
||||
s = env_->DeleteFile(fname);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// REQUIRES: wal_numbers are sorted in ascending order
|
||||
Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only,
|
||||
bool* corrupted_wal_found) {
|
||||
bool* corrupted_wal_found,
|
||||
RecoveryContext* recovery_ctx) {
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
Logger* info_log;
|
||||
@@ -1291,44 +1310,36 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
// VersionSet::next_file_number_ always to be strictly greater than any
|
||||
// log number
|
||||
versions_->MarkFileNumberUsed(max_wal_number + 1);
|
||||
assert(recovery_ctx != nullptr);
|
||||
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
autovector<const MutableCFOptions*> cf_opts;
|
||||
autovector<autovector<VersionEdit*>> edit_lists;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
cfds.push_back(cfd);
|
||||
cf_opts.push_back(cfd->GetLatestMutableCFOptions());
|
||||
auto iter = version_edits.find(cfd->GetID());
|
||||
assert(iter != version_edits.end());
|
||||
edit_lists.push_back({&iter->second});
|
||||
recovery_ctx->UpdateVersionEdits(cfd, iter->second);
|
||||
}
|
||||
|
||||
std::unique_ptr<VersionEdit> wal_deletion;
|
||||
if (flushed) {
|
||||
wal_deletion = std::make_unique<VersionEdit>();
|
||||
VersionEdit wal_deletion;
|
||||
if (immutable_db_options_.track_and_verify_wals_in_manifest) {
|
||||
wal_deletion->DeleteWalsBefore(max_wal_number + 1);
|
||||
wal_deletion.DeleteWalsBefore(max_wal_number + 1);
|
||||
}
|
||||
if (!allow_2pc()) {
|
||||
// In non-2pc mode, flushing the memtables of the column families
|
||||
// means we can advance min_log_number_to_keep.
|
||||
wal_deletion->SetMinLogNumberToKeep(max_wal_number + 1);
|
||||
wal_deletion.SetMinLogNumberToKeep(max_wal_number + 1);
|
||||
}
|
||||
edit_lists.back().push_back(wal_deletion.get());
|
||||
assert(versions_->GetColumnFamilySet() != nullptr);
|
||||
recovery_ctx->UpdateVersionEdits(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), wal_deletion);
|
||||
}
|
||||
|
||||
// write MANIFEST with update
|
||||
status = versions_->LogAndApply(cfds, cf_opts, edit_lists, &mutex_,
|
||||
directories_.GetDbDir(),
|
||||
/*new_descriptor_log=*/true);
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
if (data_seen && !flushed) {
|
||||
status = RestoreAliveLogFiles(wal_numbers);
|
||||
} else {
|
||||
// If there's no data in the WAL, or we flushed all the data, still
|
||||
} else if (!wal_numbers.empty()) { // If there's no data in the WAL, or we
|
||||
// flushed all the data, still
|
||||
// truncate the log file. If the process goes into a crash loop before
|
||||
// the file is deleted, the preallocated space will never get freed.
|
||||
const bool truncate = !read_only;
|
||||
@@ -1724,6 +1735,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
|
||||
*dbptr = nullptr;
|
||||
assert(handles);
|
||||
handles->clear();
|
||||
|
||||
size_t max_write_buffer_size = 0;
|
||||
@@ -1766,11 +1778,13 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
|
||||
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
||||
|
||||
RecoveryContext recovery_ctx;
|
||||
impl->mutex_.Lock();
|
||||
|
||||
// Handles create_if_missing, error_if_exists
|
||||
uint64_t recovered_seq(kMaxSequenceNumber);
|
||||
s = impl->Recover(column_families, false, false, false, &recovered_seq);
|
||||
s = impl->Recover(column_families, false, false, false, &recovered_seq,
|
||||
&recovery_ctx);
|
||||
if (s.ok()) {
|
||||
uint64_t new_log_number = impl->versions_->NewFileNumber();
|
||||
log::Writer* new_log = nullptr;
|
||||
@@ -1787,40 +1801,6 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// set column family handles
|
||||
for (auto cf : column_families) {
|
||||
auto cfd =
|
||||
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
||||
if (cfd != nullptr) {
|
||||
handles->push_back(
|
||||
new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
|
||||
impl->NewThreadStatusCfInfo(cfd);
|
||||
} else {
|
||||
if (db_options.create_missing_column_families) {
|
||||
// missing column family, create it
|
||||
ColumnFamilyHandle* handle;
|
||||
impl->mutex_.Unlock();
|
||||
s = impl->CreateColumnFamily(cf.options, cf.name, &handle);
|
||||
impl->mutex_.Lock();
|
||||
if (s.ok()) {
|
||||
handles->push_back(handle);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
s = Status::InvalidArgument("Column family not found", cf.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
||||
impl->InstallSuperVersionAndScheduleWork(
|
||||
cfd, &sv_context, *cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
sv_context.Clean();
|
||||
if (impl->two_write_queues_) {
|
||||
impl->log_write_mutex_.Lock();
|
||||
}
|
||||
@@ -1860,6 +1840,53 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = impl->LogAndApplyForRecovery(recovery_ctx);
|
||||
}
|
||||
|
||||
if (s.ok() && impl->immutable_db_options_.persist_stats_to_disk) {
|
||||
impl->mutex_.AssertHeld();
|
||||
s = impl->InitPersistStatsColumnFamily();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// set column family handles
|
||||
for (auto cf : column_families) {
|
||||
auto cfd =
|
||||
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
||||
if (cfd != nullptr) {
|
||||
handles->push_back(
|
||||
new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
|
||||
impl->NewThreadStatusCfInfo(cfd);
|
||||
} else {
|
||||
if (db_options.create_missing_column_families) {
|
||||
// missing column family, create it
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
impl->mutex_.Unlock();
|
||||
s = impl->CreateColumnFamily(cf.options, cf.name, &handle);
|
||||
impl->mutex_.Lock();
|
||||
if (s.ok()) {
|
||||
handles->push_back(handle);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
s = Status::InvalidArgument("Column family not found", cf.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
||||
impl->InstallSuperVersionAndScheduleWork(
|
||||
cfd, &sv_context, *cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
sv_context.Clean();
|
||||
}
|
||||
|
||||
if (s.ok() && impl->immutable_db_options_.persist_stats_to_disk) {
|
||||
// try to read format version
|
||||
s = impl->PersistentStatsProcessFormatVersion();
|
||||
|
||||
@@ -33,7 +33,8 @@ DBImplSecondary::~DBImplSecondary() {}
|
||||
Status DBImplSecondary::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool /*readonly*/, bool /*error_if_wal_file_exists*/,
|
||||
bool /*error_if_data_exists_in_wals*/, uint64_t*) {
|
||||
bool /*error_if_data_exists_in_wals*/, uint64_t*,
|
||||
RecoveryContext* /*recovery_ctx*/) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
JobContext job_context(0);
|
||||
|
||||
@@ -81,8 +81,8 @@ class DBImplSecondary : public DBImpl {
|
||||
// and log_readers_ to facilitate future operations.
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only, bool error_if_wal_file_exists,
|
||||
bool error_if_data_exists_in_wals,
|
||||
uint64_t* = nullptr) override;
|
||||
bool error_if_data_exists_in_wals, uint64_t* = nullptr,
|
||||
RecoveryContext* recovery_ctx = nullptr) override;
|
||||
|
||||
// Implementations of the DB interface
|
||||
using DB::Get;
|
||||
|
||||
@@ -1105,17 +1105,16 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
// Note: there does not seem to be a reason to wait for parallel sync at
|
||||
// this early step but it is not important since parallel sync (SyncWAL) and
|
||||
// need_log_sync are usually not used together.
|
||||
while (logs_.front().getting_synced) {
|
||||
while (logs_.front().IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
}
|
||||
for (auto& log : logs_) {
|
||||
assert(!log.getting_synced);
|
||||
// This is just to prevent the logs to be synced by a parallel SyncWAL
|
||||
// call. We will do the actual syncing later after we will write to the
|
||||
// WAL.
|
||||
// Note: there does not seem to be a reason to set this early before we
|
||||
// actually write to the WAL
|
||||
log.getting_synced = true;
|
||||
log.PrepareForSync();
|
||||
}
|
||||
} else {
|
||||
*need_log_sync = false;
|
||||
|
||||
@@ -2165,6 +2165,59 @@ TEST_F(DBWALTest, ReadOnlyRecoveryNoTruncate) {
|
||||
#endif // ROCKSDB_FALLOCATE_PRESENT
|
||||
#endif // ROCKSDB_PLATFORM_POSIX
|
||||
|
||||
TEST_F(DBWALTest, WalInManifestButNotInSortedWals) {
|
||||
Options options = CurrentOptions();
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency;
|
||||
|
||||
// Build a way to make wal files selectively go missing
|
||||
bool wals_go_missing = false;
|
||||
struct MissingWalFs : public FileSystemWrapper {
|
||||
MissingWalFs(const std::shared_ptr<FileSystem>& t,
|
||||
bool* _wals_go_missing_flag)
|
||||
: FileSystemWrapper(t), wals_go_missing_flag(_wals_go_missing_flag) {}
|
||||
bool* wals_go_missing_flag;
|
||||
IOStatus GetChildren(const std::string& dir, const IOOptions& io_opts,
|
||||
std::vector<std::string>* r,
|
||||
IODebugContext* dbg) override {
|
||||
IOStatus s = target_->GetChildren(dir, io_opts, r, dbg);
|
||||
if (s.ok() && *wals_go_missing_flag) {
|
||||
for (size_t i = 0; i < r->size();) {
|
||||
if (EndsWith(r->at(i), ".log")) {
|
||||
r->erase(r->begin() + i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
const char* Name() const override { return "MissingWalFs"; }
|
||||
};
|
||||
auto my_fs =
|
||||
std::make_shared<MissingWalFs>(env_->GetFileSystem(), &wals_go_missing);
|
||||
std::unique_ptr<Env> my_env(NewCompositeEnv(my_fs));
|
||||
options.env = my_env.get();
|
||||
|
||||
CreateAndReopenWithCF({"blah"}, options);
|
||||
|
||||
// Currently necessary to get a WAL tracked in manifest; see
|
||||
// https://github.com/facebook/rocksdb/issues/10080
|
||||
ASSERT_OK(Put(0, "x", "y"));
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
ASSERT_OK(Put(1, "x", "y"));
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_FALSE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
std::vector<std::unique_ptr<LogFile>> wals;
|
||||
ASSERT_OK(db_->GetSortedWalFiles(wals));
|
||||
wals_go_missing = true;
|
||||
ASSERT_NOK(db_->GetSortedWalFiles(wals));
|
||||
wals_go_missing = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBWALTest, WalTermTest) {
|
||||
|
||||
@@ -334,6 +334,41 @@ TEST_P(DBWriteTest, ManualWalFlushInEffect) {
|
||||
ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty());
|
||||
}
|
||||
|
||||
TEST_P(DBWriteTest, UnflushedPutRaceWithTrackedWalSync) {
|
||||
// Repro race condition bug where unflushed WAL data extended the synced size
|
||||
// recorded to MANIFEST despite being unrecoverable.
|
||||
Options options = GetOptions();
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
options.env = fault_env.get();
|
||||
options.manual_wal_flush = true;
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::SyncWAL:Begin",
|
||||
[this](void* /* arg */) { ASSERT_OK(Put("key2", "val2")); });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->FlushWAL(true /* sync */));
|
||||
|
||||
// Ensure callback ran.
|
||||
ASSERT_EQ("val2", Get("key2"));
|
||||
|
||||
Close();
|
||||
|
||||
// Simulate full loss of unsynced data. This drops "key2" -> "val2" from the
|
||||
// DB WAL.
|
||||
fault_env->DropUnsyncedFileData();
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// Need to close before `fault_env` goes out of scope.
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(DBWriteTest, IOErrorOnWALWriteTriggersReadOnlyMode) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> mock_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
|
||||
Vendored
+3
-3
@@ -134,8 +134,8 @@ class PosixClock : public SystemClock {
|
||||
const char* NickName() const override { return kClassName(); }
|
||||
|
||||
uint64_t NowMicros() override {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, nullptr);
|
||||
port::TimeVal tv;
|
||||
port::GetTimeOfDay(&tv, nullptr);
|
||||
return static_cast<uint64_t>(tv.tv_sec) * 1000000 + tv.tv_usec;
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ class PosixClock : public SystemClock {
|
||||
dummy.reserve(maxsize);
|
||||
dummy.resize(maxsize);
|
||||
char* p = &dummy[0];
|
||||
localtime_r(&seconds, &t);
|
||||
port::LocalTimeR(&seconds, &t);
|
||||
snprintf(p, maxsize, "%04d/%02d/%02d-%02d:%02d:%02d ", t.tm_year + 1900,
|
||||
t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
|
||||
return dummy;
|
||||
|
||||
Vendored
+5
-5
@@ -1604,9 +1604,9 @@ class TestLogger : public Logger {
|
||||
|
||||
if (new_format[0] == '[') {
|
||||
// "[DEBUG] "
|
||||
ASSERT_TRUE(n <= 56 + (512 - static_cast<int>(sizeof(struct timeval))));
|
||||
ASSERT_TRUE(n <= 56 + (512 - static_cast<int>(sizeof(port::TimeVal))));
|
||||
} else {
|
||||
ASSERT_TRUE(n <= 48 + (512 - static_cast<int>(sizeof(struct timeval))));
|
||||
ASSERT_TRUE(n <= 48 + (512 - static_cast<int>(sizeof(port::TimeVal))));
|
||||
}
|
||||
va_end(backup_ap);
|
||||
}
|
||||
@@ -1674,9 +1674,9 @@ class TestLogger2 : public Logger {
|
||||
va_copy(backup_ap, ap);
|
||||
int n = vsnprintf(new_format, sizeof(new_format) - 1, format, backup_ap);
|
||||
// 48 bytes for extra information + bytes allocated
|
||||
ASSERT_TRUE(
|
||||
n <= 48 + static_cast<int>(max_log_size_ - sizeof(struct timeval)));
|
||||
ASSERT_TRUE(n > static_cast<int>(max_log_size_ - sizeof(struct timeval)));
|
||||
ASSERT_TRUE(n <=
|
||||
48 + static_cast<int>(max_log_size_ - sizeof(port::TimeVal)));
|
||||
ASSERT_TRUE(n > static_cast<int>(max_log_size_ - sizeof(port::TimeVal)));
|
||||
va_end(backup_ap);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+45
@@ -338,6 +338,51 @@ IOStatus FSRandomAccessFileTracingWrapper::InvalidateCache(size_t offset,
|
||||
return s;
|
||||
}
|
||||
|
||||
IOStatus FSRandomAccessFileTracingWrapper::ReadAsync(
|
||||
FSReadRequest& req, const IOOptions& opts,
|
||||
std::function<void(const FSReadRequest&, void*)> cb, void* cb_arg,
|
||||
void** io_handle, IOHandleDeleter* del_fn, IODebugContext* dbg) {
|
||||
// Create a callback and populate info.
|
||||
auto read_async_callback =
|
||||
std::bind(&FSRandomAccessFileTracingWrapper::ReadAsyncCallback, this,
|
||||
std::placeholders::_1, std::placeholders::_2);
|
||||
ReadAsyncCallbackInfo* read_async_cb_info = new ReadAsyncCallbackInfo;
|
||||
read_async_cb_info->cb_ = cb;
|
||||
read_async_cb_info->cb_arg_ = cb_arg;
|
||||
read_async_cb_info->start_time_ = clock_->NowNanos();
|
||||
read_async_cb_info->file_op_ = __func__;
|
||||
|
||||
IOStatus s = target()->ReadAsync(req, opts, read_async_callback,
|
||||
read_async_cb_info, io_handle, del_fn, dbg);
|
||||
|
||||
if (!s.ok()) {
|
||||
delete read_async_cb_info;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void FSRandomAccessFileTracingWrapper::ReadAsyncCallback(
|
||||
const FSReadRequest& req, void* cb_arg) {
|
||||
ReadAsyncCallbackInfo* read_async_cb_info =
|
||||
static_cast<ReadAsyncCallbackInfo*>(cb_arg);
|
||||
assert(read_async_cb_info);
|
||||
assert(read_async_cb_info->cb_);
|
||||
|
||||
uint64_t elapsed = clock_->NowNanos() - read_async_cb_info->start_time_;
|
||||
uint64_t io_op_data = 0;
|
||||
io_op_data |= (1 << IOTraceOp::kIOLen);
|
||||
io_op_data |= (1 << IOTraceOp::kIOOffset);
|
||||
IOTraceRecord io_record(clock_->NowNanos(), TraceType::kIOTracer, io_op_data,
|
||||
read_async_cb_info->file_op_, elapsed,
|
||||
req.status.ToString(), file_name_, req.result.size(),
|
||||
req.offset);
|
||||
io_tracer_->WriteIOOp(io_record, nullptr /*dbg*/);
|
||||
|
||||
// call the underlying callback.
|
||||
read_async_cb_info->cb_(req, read_async_cb_info->cb_arg_);
|
||||
delete read_async_cb_info;
|
||||
}
|
||||
|
||||
IOStatus FSWritableFileTracingWrapper::Append(const Slice& data,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
|
||||
Vendored
+14
@@ -228,11 +228,25 @@ class FSRandomAccessFileTracingWrapper : public FSRandomAccessFileOwnerWrapper {
|
||||
|
||||
IOStatus InvalidateCache(size_t offset, size_t length) override;
|
||||
|
||||
IOStatus ReadAsync(FSReadRequest& req, const IOOptions& opts,
|
||||
std::function<void(const FSReadRequest&, void*)> cb,
|
||||
void* cb_arg, void** io_handle, IOHandleDeleter* del_fn,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
void ReadAsyncCallback(const FSReadRequest& req, void* cb_arg);
|
||||
|
||||
private:
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
SystemClock* clock_;
|
||||
// Stores file name instead of full path.
|
||||
std::string file_name_;
|
||||
|
||||
struct ReadAsyncCallbackInfo {
|
||||
uint64_t start_time_;
|
||||
std::function<void(const FSReadRequest&, void*)> cb_;
|
||||
void* cb_arg_;
|
||||
std::string file_op_;
|
||||
};
|
||||
};
|
||||
|
||||
// The FSRandomAccessFilePtr is a wrapper class that takes pointer to storage
|
||||
|
||||
Vendored
+3
-3
@@ -509,13 +509,13 @@ class TestMemLogger : public Logger {
|
||||
char* p = base;
|
||||
char* limit = base + bufsize;
|
||||
|
||||
struct timeval now_tv;
|
||||
gettimeofday(&now_tv, nullptr);
|
||||
port::TimeVal now_tv;
|
||||
port::GetTimeOfDay(&now_tv, nullptr);
|
||||
const time_t seconds = now_tv.tv_sec;
|
||||
struct tm t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
struct tm* ret __attribute__((__unused__));
|
||||
ret = localtime_r(&seconds, &t);
|
||||
ret = port::LocalTimeR(&seconds, &t);
|
||||
assert(ret);
|
||||
p += snprintf(p, limit - p, "%04d/%02d/%02d-%02d:%02d:%02d.%06d ",
|
||||
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#ifdef GFLAGS
|
||||
#include "tools/io_tracer_parser_tool.h"
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -1266,6 +1269,33 @@ class PrefetchTestWithPosix : public DBTestBase,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
public:
|
||||
PrefetchTestWithPosix() : DBTestBase("prefetch_test_with_posix", true) {}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#ifdef GFLAGS
|
||||
const int kMaxArgCount = 100;
|
||||
const size_t kArgBufferSize = 100000;
|
||||
|
||||
void RunIOTracerParserTool(std::string trace_file) {
|
||||
std::vector<std::string> params = {"./io_tracer_parser",
|
||||
"-io_trace_file=" + trace_file};
|
||||
|
||||
char arg_buffer[kArgBufferSize];
|
||||
char* argv[kMaxArgCount];
|
||||
int argc = 0;
|
||||
int cursor = 0;
|
||||
for (const auto& arg : params) {
|
||||
ASSERT_LE(cursor + arg.size() + 1, kArgBufferSize);
|
||||
ASSERT_LE(argc + 1, kMaxArgCount);
|
||||
|
||||
snprintf(arg_buffer + cursor, arg.size() + 1, "%s", arg.c_str());
|
||||
|
||||
argv[argc++] = arg_buffer + cursor;
|
||||
cursor += static_cast<int>(arg.size()) + 1;
|
||||
}
|
||||
ASSERT_EQ(0, ROCKSDB_NAMESPACE::io_tracer_parser(argc, argv));
|
||||
}
|
||||
#endif // GFLAGS
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PrefetchTestWithPosix, PrefetchTestWithPosix,
|
||||
@@ -1433,6 +1463,133 @@ TEST_P(PrefetchTestWithPosix, ReadAsyncWithPosixFS) {
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#ifdef GFLAGS
|
||||
TEST_P(PrefetchTestWithPosix, TraceReadAsyncWithCallbackWrapper) {
|
||||
if (mem_env_ || encrypted_env_) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment");
|
||||
return;
|
||||
}
|
||||
|
||||
const int kNumKeys = 1000;
|
||||
std::shared_ptr<MockFS> fs = std::make_shared<MockFS>(
|
||||
FileSystem::Default(), /*support_prefetch=*/false);
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, fs));
|
||||
|
||||
bool use_direct_io = false;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 1024;
|
||||
options.create_if_missing = true;
|
||||
options.compression = kNoCompression;
|
||||
options.env = env.get();
|
||||
options.statistics = CreateDBStatistics();
|
||||
if (use_direct_io) {
|
||||
options.use_direct_reads = true;
|
||||
options.use_direct_io_for_flush_and_compaction = true;
|
||||
}
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
table_options.metadata_block_size = 1024;
|
||||
table_options.index_type =
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
Status s = TryReopen(options);
|
||||
if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) {
|
||||
// If direct IO is not supported, skip the test
|
||||
return;
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
int total_keys = 0;
|
||||
// Write the keys.
|
||||
{
|
||||
WriteBatch batch;
|
||||
Random rnd(309);
|
||||
for (int j = 0; j < 5; j++) {
|
||||
for (int i = j * kNumKeys; i < (j + 1) * kNumKeys; i++) {
|
||||
ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000)));
|
||||
total_keys++;
|
||||
}
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
MoveFilesToLevel(2);
|
||||
}
|
||||
|
||||
int buff_prefetch_count = 0;
|
||||
bool read_async_called = false;
|
||||
ReadOptions ro;
|
||||
ro.adaptive_readahead = true;
|
||||
ro.async_io = true;
|
||||
|
||||
if (GetParam()) {
|
||||
ro.readahead_size = 16 * 1024;
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FilePrefetchBuffer::PrefetchAsyncInternal:Start",
|
||||
[&](void*) { buff_prefetch_count++; });
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"UpdateResults::io_uring_result",
|
||||
[&](void* /*arg*/) { read_async_called = true; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Read the keys.
|
||||
{
|
||||
// Start io_tracing.
|
||||
WriteOptions write_opt;
|
||||
TraceOptions trace_opt;
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
std::string trace_file_path = dbname_ + "/io_trace_file";
|
||||
|
||||
ASSERT_OK(
|
||||
NewFileTraceWriter(env_, EnvOptions(), trace_file_path, &trace_writer));
|
||||
ASSERT_OK(db_->StartIOTrace(trace_opt, std::move(trace_writer)));
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
|
||||
auto iter = std::unique_ptr<Iterator>(db_->NewIterator(ro));
|
||||
int num_keys = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_OK(iter->status());
|
||||
num_keys++;
|
||||
}
|
||||
|
||||
// End the tracing.
|
||||
ASSERT_OK(db_->EndIOTrace());
|
||||
ASSERT_OK(env_->FileExists(trace_file_path));
|
||||
|
||||
ASSERT_EQ(num_keys, total_keys);
|
||||
ASSERT_GT(buff_prefetch_count, 0);
|
||||
|
||||
// Check stats to make sure async prefetch is done.
|
||||
{
|
||||
HistogramData async_read_bytes;
|
||||
options.statistics->histogramData(ASYNC_READ_BYTES, &async_read_bytes);
|
||||
// Not all platforms support iouring. In that case, ReadAsync in posix
|
||||
// won't submit async requests.
|
||||
if (read_async_called) {
|
||||
ASSERT_GT(async_read_bytes.count, 0);
|
||||
} else {
|
||||
ASSERT_EQ(async_read_bytes.count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the file to see if ReadAsync is logged.
|
||||
RunIOTracerParserTool(trace_file_path);
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
Close();
|
||||
}
|
||||
#endif // GFLAGS
|
||||
#endif // ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -457,9 +457,16 @@ IOStatus RandomAccessFileReader::ReadAsync(
|
||||
|
||||
IOStatus s = file_->ReadAsync(req, opts, read_async_callback, read_async_info,
|
||||
io_handle, del_fn, nullptr /*dbg*/);
|
||||
// Suppress false positive clang analyzer warnings.
|
||||
// Memory is not released if file_->ReadAsync returns !s.ok(), because
|
||||
// ReadAsyncCallback is never called in that case. If ReadAsyncCallback is
|
||||
// called then ReadAsync should always return IOStatus::OK().
|
||||
#ifndef __clang_analyzer__
|
||||
if (!s.ok()) {
|
||||
delete read_async_info;
|
||||
}
|
||||
#endif // __clang_analyzer__
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,8 @@ IOStatus WritableFileWriter::Append(const Slice& data, uint32_t crc32c_checksum,
|
||||
|
||||
TEST_KILL_RANDOM("WritableFileWriter::Append:1");
|
||||
if (s.ok()) {
|
||||
filesize_ += data.size();
|
||||
uint64_t cur_size = filesize_.load(std::memory_order_acquire);
|
||||
filesize_.store(cur_size + data.size(), std::memory_order_release);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -191,7 +192,8 @@ IOStatus WritableFileWriter::Pad(const size_t pad_bytes,
|
||||
cap = buf_.Capacity() - buf_.CurrentSize();
|
||||
}
|
||||
pending_sync_ = true;
|
||||
filesize_ += pad_bytes;
|
||||
uint64_t cur_size = filesize_.load(std::memory_order_acquire);
|
||||
filesize_.store(cur_size + pad_bytes, std::memory_order_release);
|
||||
if (perform_data_verification_) {
|
||||
buffered_data_crc32c_checksum_ =
|
||||
crc32c::Extend(buffered_data_crc32c_checksum_,
|
||||
@@ -227,14 +229,15 @@ IOStatus WritableFileWriter::Close() {
|
||||
start_ts = FileOperationInfo::StartNow();
|
||||
}
|
||||
#endif
|
||||
interim = writable_file_->Truncate(filesize_, io_options, nullptr);
|
||||
uint64_t filesz = filesize_.load(std::memory_order_acquire);
|
||||
interim = writable_file_->Truncate(filesz, io_options, nullptr);
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (ShouldNotifyListeners()) {
|
||||
auto finish_ts = FileOperationInfo::FinishNow();
|
||||
NotifyOnFileTruncateFinish(start_ts, finish_ts, s);
|
||||
if (!interim.ok()) {
|
||||
NotifyOnIOError(interim, FileOperationType::kTruncate, file_name(),
|
||||
filesize_);
|
||||
filesz);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -372,8 +375,9 @@ IOStatus WritableFileWriter::Flush(Env::IOPriority op_rate_limiter_priority) {
|
||||
const uint64_t kBytesNotSyncRange =
|
||||
1024 * 1024; // recent 1MB is not synced.
|
||||
const uint64_t kBytesAlignWhenSync = 4 * 1024; // Align 4KB.
|
||||
if (filesize_ > kBytesNotSyncRange) {
|
||||
uint64_t offset_sync_to = filesize_ - kBytesNotSyncRange;
|
||||
uint64_t cur_size = filesize_.load(std::memory_order_acquire);
|
||||
if (cur_size > kBytesNotSyncRange) {
|
||||
uint64_t offset_sync_to = cur_size - kBytesNotSyncRange;
|
||||
offset_sync_to -= offset_sync_to % kBytesAlignWhenSync;
|
||||
assert(offset_sync_to >= last_sync_size_);
|
||||
if (offset_sync_to > 0 &&
|
||||
@@ -581,6 +585,8 @@ IOStatus WritableFileWriter::WriteBuffered(
|
||||
|
||||
left -= allowed;
|
||||
src += allowed;
|
||||
uint64_t cur_size = flushed_size_.load(std::memory_order_acquire);
|
||||
flushed_size_.store(cur_size + allowed, std::memory_order_release);
|
||||
}
|
||||
buf_.Size(0);
|
||||
buffered_data_crc32c_checksum_ = 0;
|
||||
@@ -671,6 +677,8 @@ IOStatus WritableFileWriter::WriteBufferedWithChecksum(
|
||||
// the corresponding checksum value
|
||||
buf_.Size(0);
|
||||
buffered_data_crc32c_checksum_ = 0;
|
||||
uint64_t cur_size = flushed_size_.load(std::memory_order_acquire);
|
||||
flushed_size_.store(cur_size + left, std::memory_order_release);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -778,6 +786,8 @@ IOStatus WritableFileWriter::WriteDirect(
|
||||
left -= size;
|
||||
src += size;
|
||||
write_offset += size;
|
||||
uint64_t cur_size = flushed_size_.load(std::memory_order_acquire);
|
||||
flushed_size_.store(cur_size + size, std::memory_order_release);
|
||||
assert((next_write_offset_ % alignment) == 0);
|
||||
}
|
||||
|
||||
@@ -880,6 +890,8 @@ IOStatus WritableFileWriter::WriteDirectWithChecksum(
|
||||
|
||||
IOSTATS_ADD(bytes_written, left);
|
||||
assert((next_write_offset_ % alignment) == 0);
|
||||
uint64_t cur_size = flushed_size_.load(std::memory_order_acquire);
|
||||
flushed_size_.store(cur_size + left, std::memory_order_release);
|
||||
|
||||
if (s.ok()) {
|
||||
// Move the tail to the beginning of the buffer
|
||||
|
||||
@@ -142,7 +142,8 @@ class WritableFileWriter {
|
||||
size_t max_buffer_size_;
|
||||
// Actually written data size can be used for truncate
|
||||
// not counting padding data
|
||||
uint64_t filesize_;
|
||||
std::atomic<uint64_t> filesize_;
|
||||
std::atomic<uint64_t> flushed_size_;
|
||||
#ifndef ROCKSDB_LITE
|
||||
// This is necessary when we use unbuffered access
|
||||
// and writes must happen on aligned offsets
|
||||
@@ -180,6 +181,7 @@ class WritableFileWriter {
|
||||
buf_(),
|
||||
max_buffer_size_(options.writable_file_max_buffer_size),
|
||||
filesize_(0),
|
||||
flushed_size_(0),
|
||||
#ifndef ROCKSDB_LITE
|
||||
next_write_offset_(0),
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -255,7 +257,17 @@ class WritableFileWriter {
|
||||
// returns NotSupported status.
|
||||
IOStatus SyncWithoutFlush(bool use_fsync);
|
||||
|
||||
uint64_t GetFileSize() const { return filesize_; }
|
||||
uint64_t GetFileSize() const {
|
||||
return filesize_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
// Returns the size of data flushed to the underlying `FSWritableFile`.
|
||||
// Expected to match `writable_file()->GetFileSize()`.
|
||||
// The return value can serve as a lower-bound for the amount of data synced
|
||||
// by a future call to `SyncWithoutFlush()`.
|
||||
uint64_t GetFlushedSize() const {
|
||||
return flushed_size_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
IOStatus InvalidateCache(size_t offset, size_t length) {
|
||||
return writable_file_->InvalidateCache(offset, length);
|
||||
|
||||
@@ -483,11 +483,17 @@ struct DBOptions {
|
||||
bool flush_verify_memtable_count = true;
|
||||
|
||||
// If true, the log numbers and sizes of the synced WALs are tracked
|
||||
// in MANIFEST, then during DB recovery, if a synced WAL is missing
|
||||
// in MANIFEST. During DB recovery, if a synced WAL is missing
|
||||
// from disk, or the WAL's size does not match the recorded size in
|
||||
// MANIFEST, an error will be reported and the recovery will be aborted.
|
||||
//
|
||||
// This is one additional protection against WAL corruption besides the
|
||||
// per-WAL-entry checksum.
|
||||
//
|
||||
// Note that this option does not work with secondary instance.
|
||||
// Currently, only syncing closed WALs are tracked. Calling `DB::SyncWAL()`,
|
||||
// etc. or writing with `WriteOptions::sync=true` to sync the live WAL is not
|
||||
// tracked for performance/efficiency reasons.
|
||||
//
|
||||
// Default: false
|
||||
bool track_and_verify_wals_in_manifest = false;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// minor or major version number planned for release.
|
||||
#define ROCKSDB_MAJOR 7
|
||||
#define ROCKSDB_MINOR 3
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_PATCH 2
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
// double underscore. Now we have to live with our choice. We'll deprecate these
|
||||
|
||||
@@ -1428,7 +1428,7 @@ public class RocksDBTest {
|
||||
assertThat(livefiles.manifestFileSize).isEqualTo(59);
|
||||
assertThat(livefiles.files.size()).isEqualTo(3);
|
||||
assertThat(livefiles.files.get(0)).isEqualTo("/CURRENT");
|
||||
assertThat(livefiles.files.get(1)).isEqualTo("/MANIFEST-000004");
|
||||
assertThat(livefiles.files.get(1)).isEqualTo("/MANIFEST-000005");
|
||||
assertThat(livefiles.files.get(2)).isEqualTo("/OPTIONS-000007");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +100,11 @@ class EnvLogger : public Logger {
|
||||
char* p = base;
|
||||
char* limit = base + bufsize;
|
||||
|
||||
struct timeval now_tv;
|
||||
gettimeofday(&now_tv, nullptr);
|
||||
port::TimeVal now_tv;
|
||||
port::GetTimeOfDay(&now_tv, nullptr);
|
||||
const time_t seconds = now_tv.tv_sec;
|
||||
struct tm t;
|
||||
localtime_r(&seconds, &t);
|
||||
port::LocalTimeR(&seconds, &t);
|
||||
p += snprintf(p, limit - p, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %llx ",
|
||||
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
|
||||
t.tm_min, t.tm_sec, static_cast<int>(now_tv.tv_usec),
|
||||
|
||||
@@ -27,7 +27,7 @@ void LogBuffer::AddLogToBuffer(size_t max_log_size, const char* format,
|
||||
char* limit = alloc_mem + max_log_size - 1;
|
||||
|
||||
// store the time
|
||||
gettimeofday(&(buffered_log->now_tv), nullptr);
|
||||
port::GetTimeOfDay(&(buffered_log->now_tv), nullptr);
|
||||
|
||||
// Print the message
|
||||
if (p < limit) {
|
||||
@@ -60,7 +60,7 @@ void LogBuffer::FlushBufferToLog() {
|
||||
for (BufferedLog* log : logs_) {
|
||||
const time_t seconds = log->now_tv.tv_sec;
|
||||
struct tm t;
|
||||
if (localtime_r(&seconds, &t) != nullptr) {
|
||||
if (port::LocalTimeR(&seconds, &t) != nullptr) {
|
||||
Log(log_level_, info_log_,
|
||||
"(Original Log Time %04d/%02d/%02d-%02d:%02d:%02d.%06d) %s",
|
||||
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min,
|
||||
|
||||
@@ -35,7 +35,7 @@ class LogBuffer {
|
||||
private:
|
||||
// One log entry with its timestamp
|
||||
struct BufferedLog {
|
||||
struct timeval now_tv; // Timestamp of the log
|
||||
port::TimeVal now_tv; // Timestamp of the log
|
||||
char message[1]; // Beginning of log message
|
||||
};
|
||||
|
||||
|
||||
@@ -103,11 +103,11 @@ class PosixLogger : public Logger {
|
||||
char* p = base;
|
||||
char* limit = base + bufsize;
|
||||
|
||||
struct timeval now_tv;
|
||||
gettimeofday(&now_tv, nullptr);
|
||||
port::TimeVal now_tv;
|
||||
port::GetTimeOfDay(&now_tv, nullptr);
|
||||
const time_t seconds = now_tv.tv_sec;
|
||||
struct tm t;
|
||||
localtime_r(&seconds, &t);
|
||||
port::LocalTimeR(&seconds, &t);
|
||||
p += snprintf(p, limit - p, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %llu ",
|
||||
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
|
||||
t.tm_min, t.tm_sec, static_cast<int>(now_tv.tv_usec),
|
||||
|
||||
@@ -604,10 +604,14 @@ TEST_F(StatsHistoryTest, ForceManualFlushStatsCF) {
|
||||
dbfull()->TEST_WaitForStatsDumpRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(kPeriodSec); });
|
||||
// writing to all three cf, flush default cf
|
||||
// LogNumbers: default: 14, stats: 4, pikachu: 4
|
||||
// LogNumbers: default: 16, stats: 10, pikachu: 5
|
||||
// Since in recovery process, cfd_stats column is created after WAL is
|
||||
// created, synced and MANIFEST is persisted, its log number which depends on
|
||||
// logfile_number_ will be different. Since "pikachu" is never flushed, thus
|
||||
// its log_number should be the smallest of the three.
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_test->GetLogNumber());
|
||||
ASSERT_LT(cfd_stats->GetLogNumber(), cfd_default->GetLogNumber());
|
||||
ASSERT_LT(cfd_test->GetLogNumber(), cfd_stats->GetLogNumber());
|
||||
ASSERT_LT(cfd_test->GetLogNumber(), cfd_default->GetLogNumber());
|
||||
|
||||
ASSERT_OK(Put("foo1", "v1"));
|
||||
ASSERT_OK(Put("bar1", "v1"));
|
||||
|
||||
+27
-11
@@ -12,36 +12,52 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(OS_WIN) && defined(_MSC_VER)
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
#if defined(OS_WIN) && (defined(_MSC_VER) || defined(__MINGW32__))
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace port {
|
||||
|
||||
// Avoid including winsock2.h for this definition
|
||||
struct timeval {
|
||||
struct TimeVal {
|
||||
long tv_sec;
|
||||
long tv_usec;
|
||||
};
|
||||
|
||||
void gettimeofday(struct timeval* tv, struct timezone* tz);
|
||||
void GetTimeOfDay(TimeVal* tv, struct timezone* tz);
|
||||
|
||||
inline struct tm* localtime_r(const time_t* timep, struct tm* result) {
|
||||
inline struct tm* LocalTimeR(const time_t* timep, struct tm* result) {
|
||||
errno_t ret = localtime_s(result, timep);
|
||||
return (ret == 0) ? result : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
using port::timeval;
|
||||
using port::gettimeofday;
|
||||
using port::localtime_r;
|
||||
} // namespace port
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#else
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace port {
|
||||
|
||||
using TimeVal = struct timeval;
|
||||
|
||||
inline void GetTimeOfDay(TimeVal* tv, struct timezone* tz) {
|
||||
gettimeofday(tv, tz);
|
||||
}
|
||||
|
||||
inline struct tm* LocalTimeR(const time_t* timep, struct tm* result) {
|
||||
return localtime_r(timep, result);
|
||||
}
|
||||
|
||||
} // namespace port
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
||||
@@ -52,7 +52,7 @@ std::wstring utf8_to_utf16(const std::string& utf8) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void gettimeofday(struct timeval* tv, struct timezone* /* tz */) {
|
||||
void GetTimeOfDay(TimeVal* tv, struct timezone* /* tz */) {
|
||||
std::chrono::microseconds usNow(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()));
|
||||
|
||||
@@ -118,8 +118,8 @@ void WinLogger::Logv(const char* format, va_list ap) {
|
||||
char* p = base;
|
||||
char* limit = base + bufsize;
|
||||
|
||||
struct timeval now_tv;
|
||||
gettimeofday(&now_tv, nullptr);
|
||||
port::TimeVal now_tv;
|
||||
port::GetTimeOfDay(&now_tv, nullptr);
|
||||
const time_t seconds = now_tv.tv_sec;
|
||||
struct tm t;
|
||||
localtime_s(&t, &seconds);
|
||||
|
||||
@@ -1620,6 +1620,9 @@ static const bool FLAGS_table_cache_numshardbits_dummy __attribute__((__unused__
|
||||
RegisterFlagValidator(&FLAGS_table_cache_numshardbits,
|
||||
&ValidateTableCacheNumshardbits);
|
||||
|
||||
DEFINE_bool(track_and_verify_wals_in_manifest, false,
|
||||
"If true, enable WAL tracking in the MANIFEST");
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace {
|
||||
static Status CreateMemTableRepFactory(
|
||||
@@ -4392,6 +4395,9 @@ class Benchmark {
|
||||
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
|
||||
}
|
||||
|
||||
options.track_and_verify_wals_in_manifest =
|
||||
FLAGS_track_and_verify_wals_in_manifest;
|
||||
|
||||
// Integrated BlobDB
|
||||
options.enable_blob_files = FLAGS_enable_blob_files;
|
||||
options.min_blob_size = FLAGS_min_blob_size;
|
||||
|
||||
@@ -54,7 +54,8 @@ class AsyncFileReader {
|
||||
file_(file),
|
||||
opts_(opts),
|
||||
read_reqs_(read_reqs),
|
||||
num_reqs_(num_reqs) {}
|
||||
num_reqs_(num_reqs),
|
||||
next_(nullptr) {}
|
||||
|
||||
bool await_ready() noexcept { return false; }
|
||||
|
||||
@@ -62,7 +63,7 @@ class AsyncFileReader {
|
||||
// awaiting_coro parameter is the handle of the awaiter. The handle can be
|
||||
// resumed later, so we cache it here.
|
||||
bool await_suspend(
|
||||
std::experimental::coroutine_handle<> awaiting_coro) noexcept {
|
||||
folly::coro::impl::coroutine_handle<> awaiting_coro) noexcept {
|
||||
awaiting_coro_ = awaiting_coro;
|
||||
// MultiReadAsyncImpl always returns true, so caller will be suspended
|
||||
return reader_.MultiReadAsyncImpl(this);
|
||||
@@ -83,7 +84,7 @@ class AsyncFileReader {
|
||||
size_t num_reqs_;
|
||||
autovector<void*, 32> io_handle_;
|
||||
autovector<IOHandleDeleter, 32> del_fn_;
|
||||
std::experimental::coroutine_handle<> awaiting_coro_;
|
||||
folly::coro::impl::coroutine_handle<> awaiting_coro_;
|
||||
// Use this to link to the next ReadAwaiter in the suspended coroutine
|
||||
// list. The head and tail of the list are tracked by AsyncFileReader.
|
||||
// We use this approach rather than an STL container in order to avoid
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@ std::string TimeToHumanString(int unixtime) {
|
||||
char time_buffer[80];
|
||||
time_t rawtime = unixtime;
|
||||
struct tm tInfo;
|
||||
struct tm* timeinfo = localtime_r(&rawtime, &tInfo);
|
||||
struct tm* timeinfo = port::LocalTimeR(&rawtime, &tInfo);
|
||||
assert(timeinfo == &tInfo);
|
||||
strftime(time_buffer, 80, "%c", timeinfo);
|
||||
return std::string(time_buffer);
|
||||
|
||||
@@ -915,6 +915,51 @@ TEST_F(CheckpointTest, CheckpointWithDbPath) {
|
||||
delete checkpoint;
|
||||
}
|
||||
|
||||
TEST_F(CheckpointTest, PutRaceWithCheckpointTrackedWalSync) {
|
||||
// Repro for a race condition where a user write comes in after the checkpoint
|
||||
// syncs WAL for `track_and_verify_wals_in_manifest` but before the
|
||||
// corresponding MANIFEST update. With the bug, that scenario resulted in an
|
||||
// unopenable DB with error "Corruption: Size mismatch: WAL ...".
|
||||
Options options = CurrentOptions();
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
options.env = fault_env.get();
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::SyncWAL:BeforeMarkLogsSynced:1",
|
||||
[this](void* /* arg */) { ASSERT_OK(Put("key2", "val2")); });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::unique_ptr<Checkpoint> checkpoint;
|
||||
{
|
||||
Checkpoint* checkpoint_ptr;
|
||||
ASSERT_OK(Checkpoint::Create(db_, &checkpoint_ptr));
|
||||
checkpoint.reset(checkpoint_ptr);
|
||||
}
|
||||
|
||||
ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_));
|
||||
|
||||
// Ensure callback ran.
|
||||
ASSERT_EQ("val2", Get("key2"));
|
||||
|
||||
Close();
|
||||
|
||||
// Simulate full loss of unsynced data. This drops "key2" -> "val2" from the
|
||||
// DB WAL.
|
||||
fault_env->DropUnsyncedFileData();
|
||||
|
||||
// Before the bug fix, reopening the DB would fail because the MANIFEST's
|
||||
// AddWal entry indicated the WAL should be synced through "key2" -> "val2".
|
||||
Reopen(options);
|
||||
|
||||
// Need to close before `fault_env` goes out of scope.
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -11,14 +11,16 @@
|
||||
int main() { fprintf(stderr, "Please install gflags to run tools\n"); }
|
||||
#else
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "port/port_posix.h"
|
||||
#include "port/sys_time.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/mutexlock.h"
|
||||
@@ -152,8 +154,8 @@ class HashTableBenchmark {
|
||||
}
|
||||
|
||||
static uint64_t NowInMillSec() {
|
||||
timeval tv;
|
||||
gettimeofday(&tv, /*tz=*/nullptr);
|
||||
port::TimeVal tv;
|
||||
port::GetTimeOfDay(&tv, /*tz=*/nullptr);
|
||||
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user