Fix segfalut in ~DBWithTTLImpl() when called after Close() (#5485)

Summary:
~DBWithTTLImpl() fails after calling Close() function (will invoke the
Close() function of DBImpl), because the Close() function deletes
default_cf_handle_ which is used in the GetOptions() function called
in ~DBWithTTLImpl(), hence lead to segfault.

Fix by creating a Close() function for the DBWithTTLImpl class and do
the close and the work originally in ~DBWithTTLImpl(). If the Close()
function is not called, it will be called in the ~DBWithTTLImpl()
function.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/5485

Test Plan: make clean;  USE_CLANG=1 make all check -j

Differential Revision: D15924498

fbshipit-source-id: 567397fb972961059083a1ae0f9f99ff74872b78
This commit is contained in:
feilongliu 2019-06-20 13:04:13 -07:00 committed by Facebook Github Bot
parent 24f73436fb
commit 0b0cb6f1a2
3 changed files with 64 additions and 6 deletions

View File

@ -34,12 +34,25 @@ void DBWithTTLImpl::SanitizeOptions(int32_t ttl, ColumnFamilyOptions* options,
} }
// Open the db inside DBWithTTLImpl because options needs pointer to its ttl // Open the db inside DBWithTTLImpl because options needs pointer to its ttl
DBWithTTLImpl::DBWithTTLImpl(DB* db) : DBWithTTL(db) {} DBWithTTLImpl::DBWithTTLImpl(DB* db) : DBWithTTL(db), closed_(false) {}
DBWithTTLImpl::~DBWithTTLImpl() { DBWithTTLImpl::~DBWithTTLImpl() {
if (!closed_) {
Close();
}
}
Status DBWithTTLImpl::Close() {
Status ret = Status::OK();
if (!closed_) {
Options default_options = GetOptions();
// Need to stop background compaction before getting rid of the filter // Need to stop background compaction before getting rid of the filter
CancelAllBackgroundWork(db_, /* wait = */ true); CancelAllBackgroundWork(db_, /* wait = */ true);
delete GetOptions().compaction_filter; ret = db_->Close();
delete default_options.compaction_filter;
closed_ = true;
}
return ret;
} }
Status UtilityDB::OpenTtlDB(const Options& options, const std::string& dbname, Status UtilityDB::OpenTtlDB(const Options& options, const std::string& dbname,

View File

@ -35,6 +35,8 @@ class DBWithTTLImpl : public DBWithTTL {
virtual ~DBWithTTLImpl(); virtual ~DBWithTTLImpl();
virtual Status Close() override;
Status CreateColumnFamilyWithTtl(const ColumnFamilyOptions& options, Status CreateColumnFamilyWithTtl(const ColumnFamilyOptions& options,
const std::string& column_family_name, const std::string& column_family_name,
ColumnFamilyHandle** handle, ColumnFamilyHandle** handle,
@ -99,6 +101,10 @@ class DBWithTTLImpl : public DBWithTTL {
void SetTtl(int32_t ttl) override { SetTtl(DefaultColumnFamily(), ttl); } void SetTtl(int32_t ttl) override { SetTtl(DefaultColumnFamily(), ttl); }
void SetTtl(ColumnFamilyHandle *h, int32_t ttl) override; void SetTtl(ColumnFamilyHandle *h, int32_t ttl) override;
private:
// remember whether the Close completes or not
bool closed_;
}; };
class TtlIterator : public Iterator { class TtlIterator : public Iterator {

View File

@ -86,10 +86,25 @@ class TtlTest : public testing::Test {
ASSERT_OK(DBWithTTL::Open(options_, dbname_, &db_ttl_, ttl, true)); ASSERT_OK(DBWithTTL::Open(options_, dbname_, &db_ttl_, ttl, true));
} }
// Call db_ttl_->Close() before delete db_ttl_
void CloseTtl() { void CloseTtl() {
CloseTtlHelper(true);
}
// No db_ttl_->Close() before delete db_ttl_
void CloseTtlNoDBClose() {
CloseTtlHelper(false);
}
void CloseTtlHelper(bool close_db) {
if (db_ttl_ != nullptr) {
if (close_db) {
db_ttl_->Close();
}
delete db_ttl_; delete db_ttl_;
db_ttl_ = nullptr; db_ttl_ = nullptr;
} }
}
// Populates and returns a kv-map // Populates and returns a kv-map
void MakeKVMap(int64_t num_entries) { void MakeKVMap(int64_t num_entries) {
@ -401,6 +416,30 @@ TEST_F(TtlTest, NoEffect) {
CloseTtl(); CloseTtl();
} }
// Rerun the NoEffect test with a different version of CloseTtl
// function, where db is directly deleted without close.
TEST_F(TtlTest, DestructWithoutClose) {
MakeKVMap(kSampleSize_);
int64_t boundary1 = kSampleSize_ / 3;
int64_t boundary2 = 2 * boundary1;
OpenTtl();
PutValues(0, boundary1); //T=0: Set1 never deleted
SleepCompactCheck(1, 0, boundary1); //T=1: Set1 still there
CloseTtlNoDBClose();
OpenTtl(0);
PutValues(boundary1, boundary2 - boundary1); //T=1: Set2 never deleted
SleepCompactCheck(1, 0, boundary2); //T=2: Sets1 & 2 still there
CloseTtlNoDBClose();
OpenTtl(-1);
PutValues(boundary2, kSampleSize_ - boundary2); //T=3: Set3 never deleted
SleepCompactCheck(1, 0, kSampleSize_, true); //T=4: Sets 1,2,3 still there
CloseTtlNoDBClose();
}
// Puts a set of values and checks its presence using Get during ttl // Puts a set of values and checks its presence using Get during ttl
TEST_F(TtlTest, PresentDuringTTL) { TEST_F(TtlTest, PresentDuringTTL) {
MakeKVMap(kSampleSize_); MakeKVMap(kSampleSize_);