Fix leak or crash on failure in automatic atomic flush (#12176)

Summary:
Through code inspection in debugging an apparent leak of ColumnFamilyData in the crash test, I found a case where too few UnrefAndTryDelete() could be called on a cfd. This fixes that case, which would fail like this in the new unit test:

```
db_flush_test: db/column_family.cc:1648:
rocksdb::ColumnFamilySet::~ColumnFamilySet(): Assertion `last_ref' failed.
```

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

Test Plan: unit test added

Reviewed By: cbi42

Differential Revision: D52417071

Pulled By: pdillinger

fbshipit-source-id: 4ee33c918409cf9c1968f138e273d3347a6cc8e5
This commit is contained in:
Peter Dillinger
2023-12-26 11:04:25 -08:00
committed by Facebook GitHub Bot
parent 106058c076
commit a771a47a1b
3 changed files with 39 additions and 4 deletions
+33
View File
@@ -3035,6 +3035,39 @@ TEST_P(DBAtomicFlushTest, RollbackAfterFailToInstallResults) {
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_P(DBAtomicFlushTest, FailureInMultiCfAutomaticFlush) {
bool atomic_flush = GetParam();
auto fault_injection_env = std::make_shared<FaultInjectionTestEnv>(env_);
Options options = CurrentOptions();
options.env = fault_injection_env.get();
options.create_if_missing = true;
options.atomic_flush = atomic_flush;
const int kNumKeysTriggerFlush = 4;
options.memtable_factory.reset(
test::NewSpecialSkipListFactory(kNumKeysTriggerFlush));
CreateAndReopenWithCF({"pikachu"}, options);
ASSERT_EQ(2, handles_.size());
for (size_t cf = 0; cf < handles_.size(); ++cf) {
ASSERT_OK(Put(static_cast<int>(cf), "a", "value"));
}
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::ScheduleFlushes:PreSwitchMemtable",
[&](void* /*arg*/) { fault_injection_env->SetFilesystemActive(false); });
SyncPoint::GetInstance()->EnableProcessing();
for (int i = 1; i < kNumKeysTriggerFlush; ++i) {
ASSERT_OK(Put(0, "key" + std::to_string(i), "value" + std::to_string(i)));
}
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
// Next write after failed flush should fail.
ASSERT_NOK(Put(0, "x", "y"));
fault_injection_env->SetFilesystemActive(true);
Close();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
// In atomic flush, concurrent bg flush threads commit to the MANIFEST in
// serial, in the order of their picked memtables for each column family.
// Only when a bg flush thread finds out that its memtables are the earliest
+5 -4
View File
@@ -1563,6 +1563,8 @@ Status DBImpl::WriteRecoverableState() {
if (status.ok()) {
cached_recoverable_state_.Clear();
cached_recoverable_state_empty_ = true;
} else {
// FIXME: !ok status is untested
}
return status;
}
@@ -2060,16 +2062,15 @@ Status DBImpl::ScheduleFlushes(WriteContext* context) {
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
}
TEST_SYNC_POINT_CALLBACK("DBImpl::ScheduleFlushes:PreSwitchMemtable",
nullptr);
for (auto& cfd : cfds) {
if (!cfd->mem()->IsEmpty()) {
if (status.ok() && !cfd->mem()->IsEmpty()) {
status = SwitchMemtable(cfd, context);
}
if (cfd->UnrefAndTryDelete()) {
cfd = nullptr;
}
if (!status.ok()) {
break;
}
}
if (two_write_queues_) {
+1
View File
@@ -0,0 +1 @@
Fixed a possible memory leak or crash on a failure (such as I/O error) in automatic atomic flush of multiple column families.