Merge branch 'master' into columnfamilies

This commit is contained in:
Igor Canadi 2014-01-02 04:18:07 -08:00
commit 6de1b5b83e
28 changed files with 1012 additions and 155 deletions

View File

@ -44,5 +44,7 @@ libraries. You are on your own.
`make clean; make` will compile librocksdb.a (RocskDB static library) and all `make clean; make` will compile librocksdb.a (RocskDB static library) and all
the unit tests. You can run all unit tests with `make check`. the unit tests. You can run all unit tests with `make check`.
For shared library builds, exec `make librocksdb.so` instead.
If you followed the above steps and your compile or unit tests fail, If you followed the above steps and your compile or unit tests fail,
please submit an issue: (https://github.com/facebook/rocksdb/issues) please submit an issue: (https://github.com/facebook/rocksdb/issues)

View File

@ -50,6 +50,7 @@ VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
TESTS = \ TESTS = \
db_test \ db_test \
autovector_test \
table_properties_collector_test \ table_properties_collector_test \
arena_test \ arena_test \
auto_roll_logger_test \ auto_roll_logger_test \
@ -226,6 +227,9 @@ signal_test: util/signal_test.o $(LIBOBJECTS)
arena_test: util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS) arena_test: util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS)
$(CXX) util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS) $(CXX) util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
autovector_test: util/autovector_test.o $(LIBOBJECTS) $(TESTHARNESS)
$(CXX) util/autovector_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
table_properties_collector_test: db/table_properties_collector_test.o $(LIBOBJECTS) $(TESTHARNESS) table_properties_collector_test: db/table_properties_collector_test.o $(LIBOBJECTS) $(TESTHARNESS)
$(CXX) db/table_properties_collector_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS) $(CXX) db/table_properties_collector_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)

2
README
View File

@ -79,4 +79,4 @@ include/rocksdb/statistics.h
include/rocksdb/transaction_log.h include/rocksdb/transaction_log.h
An API to retrieve transaction logs from a database. An API to retrieve transaction logs from a database.
Design discussions are conducted in https://www.facebook.com/groups/rocksdb.dev/

View File

@ -1,3 +1,3 @@
* Detailed instructions on how to compile using fbcode and jemalloc * Detailed instructions on how to compile using fbcode and jemalloc
* Latest release is 2.5.fb * Latest release is 2.7.fb

View File

@ -189,6 +189,18 @@ EOF
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_ATOMIC_PRESENT" COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_ATOMIC_PRESENT"
fi fi
# Test whether fallocate is available
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
#include <fcntl.h>
int main() {
int fd = open("/dev/null", 0);
fallocate(fd, 0, 0, 1024);
}
EOF
if [ "$?" = 0 ]; then
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_FALLOCATE_PRESENT"
fi
# Test whether Snappy library is installed # Test whether Snappy library is installed
# http://code.google.com/p/snappy/ # http://code.google.com/p/snappy/
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF

View File

@ -8,7 +8,7 @@
# #
# create git version file # create git version file
VFILE=$ROCKSDB_ROOT/util/build_version.cc.tmp VFILE=$PWD/util/build_version.cc.tmp
trap "rm $VFILE" EXIT trap "rm $VFILE" EXIT
# check to see if git is in the path # check to see if git is in the path
@ -36,7 +36,7 @@ echo "const char* rocksdb_build_git_datetime = \"rocksdb_build_git_datetime:$(da
echo "const char* rocksdb_build_compile_date = __DATE__;" >> ${VFILE} echo "const char* rocksdb_build_compile_date = __DATE__;" >> ${VFILE}
echo "const char* rocksdb_build_compile_time = __TIME__;" >> ${VFILE} echo "const char* rocksdb_build_compile_time = __TIME__;" >> ${VFILE}
OUTFILE=$ROCKSDB_ROOT/util/build_version.cc OUTFILE=$PWD/util/build_version.cc
if [ ! -e $OUTFILE ] || ! cmp -s $VFILE $OUTFILE; then if [ ! -e $OUTFILE ] || ! cmp -s $VFILE $OUTFILE; then
cp $VFILE $OUTFILE cp $VFILE $OUTFILE
fi fi

View File

@ -54,7 +54,7 @@ RANLIB=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ranlib
CFLAGS="-B$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/gold -m64 -mtune=generic" CFLAGS="-B$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/gold -m64 -mtune=generic"
CFLAGS+=" -I $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/include -DHAVE_JEMALLOC" CFLAGS+=" -I $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/include -DHAVE_JEMALLOC"
CFLAGS+=" $LIBGCC_INCLUDE $GLIBC_INCLUDE" CFLAGS+=" $LIBGCC_INCLUDE $GLIBC_INCLUDE"
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_ATOMIC_PRESENT" CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_ATOMIC_PRESENT -DROCKSDB_FALLOCATE_PRESENT"
CFLAGS+=" -DSNAPPY -DGFLAGS -DZLIB -DBZIP2" CFLAGS+=" -DSNAPPY -DGFLAGS -DZLIB -DBZIP2"
EXEC_LDFLAGS=" -Wl,--whole-archive $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/lib/libjemalloc.a" EXEC_LDFLAGS=" -Wl,--whole-archive $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/lib/libjemalloc.a"

View File

@ -61,7 +61,7 @@ RANLIB=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ranlib
CFLAGS="-B$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/gold -m64 -mtune=generic" CFLAGS="-B$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/gold -m64 -mtune=generic"
CFLAGS+=" -nostdlib $LIBGCC_INCLUDE $GLIBC_INCLUDE" CFLAGS+=" -nostdlib $LIBGCC_INCLUDE $GLIBC_INCLUDE"
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_ATOMIC_PRESENT" CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_ATOMIC_PRESENT -DROCKSDB_FALLOCATE_PRESENT"
CFLAGS+=" -DSNAPPY -DGFLAGS -DZLIB -DBZIP2" CFLAGS+=" -DSNAPPY -DGFLAGS -DZLIB -DBZIP2"
EXEC_LDFLAGS="-Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib/ld.so" EXEC_LDFLAGS="-Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib/ld.so"

View File

@ -48,6 +48,7 @@ DEFINE_string(benchmarks,
"compact," "compact,"
"readrandom," "readrandom,"
"readseq," "readseq,"
"readtocache,"
"readreverse," "readreverse,"
"readwhilewriting," "readwhilewriting,"
"readrandomwriterandom," "readrandomwriterandom,"
@ -75,6 +76,7 @@ DEFINE_string(benchmarks,
"\tdeleteseq -- delete N keys in sequential order\n" "\tdeleteseq -- delete N keys in sequential order\n"
"\tdeleterandom -- delete N keys in random order\n" "\tdeleterandom -- delete N keys in random order\n"
"\treadseq -- read N times sequentially\n" "\treadseq -- read N times sequentially\n"
"\treadtocache -- 1 thread reading database sequentially\n"
"\treadreverse -- read N times in reverse order\n" "\treadreverse -- read N times in reverse order\n"
"\treadrandom -- read N times in random order\n" "\treadrandom -- read N times in random order\n"
"\treadmissing -- read N missing keys in random order\n" "\treadmissing -- read N missing keys in random order\n"
@ -1057,6 +1059,10 @@ class Benchmark {
method = &Benchmark::WriteRandom; method = &Benchmark::WriteRandom;
} else if (name == Slice("readseq")) { } else if (name == Slice("readseq")) {
method = &Benchmark::ReadSequential; method = &Benchmark::ReadSequential;
} else if (name == Slice("readtocache")) {
method = &Benchmark::ReadSequential;
num_threads = 1;
reads_ = num_;
} else if (name == Slice("readreverse")) { } else if (name == Slice("readreverse")) {
method = &Benchmark::ReadReverse; method = &Benchmark::ReadReverse;
} else if (name == Slice("readrandom")) { } else if (name == Slice("readrandom")) {

View File

@ -22,20 +22,34 @@ namespace rocksdb {
Status DBImpl::DisableFileDeletions() { Status DBImpl::DisableFileDeletions() {
MutexLock l(&mutex_); MutexLock l(&mutex_);
disable_delete_obsolete_files_ = true; ++disable_delete_obsolete_files_;
Log(options_.info_log, "File Deletions Disabled"); if (disable_delete_obsolete_files_ == 1) {
// if not, it has already been disabled, so don't log anything
Log(options_.info_log, "File Deletions Disabled");
}
return Status::OK(); return Status::OK();
} }
Status DBImpl::EnableFileDeletions() { Status DBImpl::EnableFileDeletions(bool force) {
DeletionState deletion_state; DeletionState deletion_state;
bool should_purge_files = false;
{ {
MutexLock l(&mutex_); MutexLock l(&mutex_);
disable_delete_obsolete_files_ = false; if (force) {
Log(options_.info_log, "File Deletions Enabled"); // if force, we need to enable file deletions right away
FindObsoleteFiles(deletion_state, true); disable_delete_obsolete_files_ = 0;
} else if (disable_delete_obsolete_files_ > 0) {
--disable_delete_obsolete_files_;
}
if (disable_delete_obsolete_files_ == 0) {
Log(options_.info_log, "File Deletions Enabled");
should_purge_files = true;
FindObsoleteFiles(deletion_state, true);
}
}
if (should_purge_files) {
PurgeObsoleteFiles(deletion_state);
} }
PurgeObsoleteFiles(deletion_state);
LogFlush(options_.info_log); LogFlush(options_.info_log);
return Status::OK(); return Status::OK();
} }

View File

@ -244,13 +244,14 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname)
mem_(new MemTable(internal_comparator_, mem_rep_factory_, mem_(new MemTable(internal_comparator_, mem_rep_factory_,
NumberLevels(), options_)), NumberLevels(), options_)),
logfile_number_(0), logfile_number_(0),
super_version_(nullptr),
tmp_batch_(), tmp_batch_(),
bg_compaction_scheduled_(0), bg_compaction_scheduled_(0),
bg_flush_scheduled_(0), bg_flush_scheduled_(0),
bg_logstats_scheduled_(false), bg_logstats_scheduled_(false),
manual_compaction_(nullptr), manual_compaction_(nullptr),
logger_(nullptr), logger_(nullptr),
disable_delete_obsolete_files_(false), disable_delete_obsolete_files_(0),
delete_obsolete_files_last_run_(options.env->NowMicros()), delete_obsolete_files_last_run_(options.env->NowMicros()),
purge_wal_files_last_run_(0), purge_wal_files_last_run_(0),
last_stats_dump_time_microsec_(0), last_stats_dump_time_microsec_(0),
@ -319,6 +320,13 @@ DBImpl::~DBImpl() {
bg_logstats_scheduled_) { bg_logstats_scheduled_) {
bg_cv_.Wait(); bg_cv_.Wait();
} }
if (super_version_ != nullptr) {
bool is_last_reference __attribute__((unused));
is_last_reference = super_version_->Unref();
assert(is_last_reference);
super_version_->Cleanup();
delete super_version_;
}
mutex_.Unlock(); mutex_.Unlock();
if (db_lock_ != nullptr) { if (db_lock_ != nullptr) {
@ -348,6 +356,13 @@ void DBImpl::TEST_Destroy_DBImpl() {
bg_logstats_scheduled_) { bg_logstats_scheduled_) {
bg_cv_.Wait(); bg_cv_.Wait();
} }
if (super_version_ != nullptr) {
bool is_last_reference __attribute__((unused));
is_last_reference = super_version_->Unref();
assert(is_last_reference);
super_version_->Cleanup();
delete super_version_;
}
// Prevent new compactions from occuring. // Prevent new compactions from occuring.
bg_work_gate_closed_ = true; bg_work_gate_closed_ = true;
@ -446,6 +461,49 @@ void DBImpl::MaybeDumpStats() {
} }
} }
// DBImpl::SuperVersion methods
DBImpl::SuperVersion::SuperVersion(const int num_memtables) {
to_delete.resize(num_memtables);
}
DBImpl::SuperVersion::~SuperVersion() {
for (auto td : to_delete) {
delete td;
}
}
DBImpl::SuperVersion* DBImpl::SuperVersion::Ref() {
refs.fetch_add(1, std::memory_order_relaxed);
return this;
}
bool DBImpl::SuperVersion::Unref() {
assert(refs > 0);
// fetch_sub returns the previous value of ref
return refs.fetch_sub(1, std::memory_order_relaxed) == 1;
}
void DBImpl::SuperVersion::Cleanup() {
assert(refs.load(std::memory_order_relaxed) == 0);
imm.UnrefAll(&to_delete);
MemTable* m = mem->Unref();
if (m != nullptr) {
to_delete.push_back(m);
}
current->Unref();
}
void DBImpl::SuperVersion::Init(MemTable* new_mem, const MemTableList& new_imm,
Version* new_current) {
mem = new_mem;
imm = new_imm;
current = new_current;
mem->Ref();
imm.RefAll();
current->Ref();
refs.store(1, std::memory_order_relaxed);
}
// Returns the list of live files in 'sst_live' and the list // Returns the list of live files in 'sst_live' and the list
// of all files in the filesystem in 'all_files'. // of all files in the filesystem in 'all_files'.
// no_full_scan = true -- never do the full scan using GetChildren() // no_full_scan = true -- never do the full scan using GetChildren()
@ -458,7 +516,7 @@ void DBImpl::FindObsoleteFiles(DeletionState& deletion_state,
mutex_.AssertHeld(); mutex_.AssertHeld();
// if deletion is disabled, do nothing // if deletion is disabled, do nothing
if (disable_delete_obsolete_files_) { if (disable_delete_obsolete_files_ > 0) {
return; return;
} }
@ -521,11 +579,6 @@ void DBImpl::FindObsoleteFiles(DeletionState& deletion_state,
// It is not necessary to hold the mutex when invoking this method. // It is not necessary to hold the mutex when invoking this method.
void DBImpl::PurgeObsoleteFiles(DeletionState& state) { void DBImpl::PurgeObsoleteFiles(DeletionState& state) {
// free pending memtables
for (auto m : state.memtables_to_free) {
delete m;
}
// check if there is anything to do // check if there is anything to do
if (!state.all_files.size() && if (!state.all_files.size() &&
!state.sst_delete_files.size() && !state.sst_delete_files.size() &&
@ -1191,13 +1244,14 @@ Status DBImpl::FlushMemTableToOutputFile(bool* madeProgress,
file_number, pending_outputs_, &deletion_state.memtables_to_free); file_number, pending_outputs_, &deletion_state.memtables_to_free);
if (s.ok()) { if (s.ok()) {
InstallSuperVersion(deletion_state);
if (madeProgress) { if (madeProgress) {
*madeProgress = 1; *madeProgress = 1;
} }
MaybeScheduleLogDBDeployStats(); MaybeScheduleLogDBDeployStats();
if (!disable_delete_obsolete_files_) { if (disable_delete_obsolete_files_ == 0) {
// add to deletion state // add to deletion state
deletion_state.log_delete_files.insert( deletion_state.log_delete_files.insert(
deletion_state.log_delete_files.end(), deletion_state.log_delete_files.end(),
@ -1251,11 +1305,17 @@ int DBImpl::FindMinimumEmptyLevelFitting(int level) {
void DBImpl::ReFitLevel(int level, int target_level) { void DBImpl::ReFitLevel(int level, int target_level) {
assert(level < NumberLevels()); assert(level < NumberLevels());
MutexLock l(&mutex_); SuperVersion* superversion_to_free = nullptr;
SuperVersion* new_superversion =
new SuperVersion(options_.max_write_buffer_number);
mutex_.Lock();
// only allow one thread refitting // only allow one thread refitting
if (refitting_level_) { if (refitting_level_) {
mutex_.Unlock();
Log(options_.info_log, "ReFitLevel: another thread is refitting"); Log(options_.info_log, "ReFitLevel: another thread is refitting");
delete new_superversion;
return; return;
} }
refitting_level_ = true; refitting_level_ = true;
@ -1291,6 +1351,8 @@ void DBImpl::ReFitLevel(int level, int target_level) {
edit.DebugString().data()); edit.DebugString().data());
auto status = versions_->LogAndApply(&edit, &mutex_); auto status = versions_->LogAndApply(&edit, &mutex_);
superversion_to_free = InstallSuperVersion(new_superversion);
new_superversion = nullptr;
Log(options_.info_log, "LogAndApply: %s\n", status.ToString().data()); Log(options_.info_log, "LogAndApply: %s\n", status.ToString().data());
@ -1302,6 +1364,10 @@ void DBImpl::ReFitLevel(int level, int target_level) {
refitting_level_ = false; refitting_level_ = false;
bg_work_gate_closed_ = false; bg_work_gate_closed_ = false;
mutex_.Unlock();
delete superversion_to_free;
delete new_superversion;
} }
int DBImpl::NumberLevels(const ColumnFamilyHandle& column_family) { int DBImpl::NumberLevels(const ColumnFamilyHandle& column_family) {
@ -1676,7 +1742,7 @@ Status DBImpl::BackgroundFlush(bool* madeProgress,
void DBImpl::BackgroundCallFlush() { void DBImpl::BackgroundCallFlush() {
bool madeProgress = false; bool madeProgress = false;
DeletionState deletion_state(options_.max_write_buffer_number); DeletionState deletion_state(options_.max_write_buffer_number, true);
assert(bg_flush_scheduled_); assert(bg_flush_scheduled_);
MutexLock l(&mutex_); MutexLock l(&mutex_);
@ -1722,7 +1788,7 @@ void DBImpl::TEST_PurgeObsoleteteWAL() {
void DBImpl::BackgroundCallCompaction() { void DBImpl::BackgroundCallCompaction() {
bool madeProgress = false; bool madeProgress = false;
DeletionState deletion_state(options_.max_write_buffer_number); DeletionState deletion_state(options_.max_write_buffer_number, true);
MaybeDumpStats(); MaybeDumpStats();
@ -1775,7 +1841,7 @@ void DBImpl::BackgroundCallCompaction() {
} }
Status DBImpl::BackgroundCompaction(bool* madeProgress, Status DBImpl::BackgroundCompaction(bool* madeProgress,
DeletionState& deletion_state) { DeletionState& deletion_state) {
*madeProgress = false; *madeProgress = false;
mutex_.AssertHeld(); mutex_.AssertHeld();
@ -1828,6 +1894,7 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress,
f->smallest, f->largest, f->smallest, f->largest,
f->smallest_seqno, f->largest_seqno); f->smallest_seqno, f->largest_seqno);
status = versions_->LogAndApply(c->edit(), &mutex_); status = versions_->LogAndApply(c->edit(), &mutex_);
InstallSuperVersion(deletion_state);
VersionSet::LevelSummaryStorage tmp; VersionSet::LevelSummaryStorage tmp;
Log(options_.info_log, "Moved #%lld to level-%d %lld bytes %s: %s\n", Log(options_.info_log, "Moved #%lld to level-%d %lld bytes %s: %s\n",
static_cast<unsigned long long>(f->number), static_cast<unsigned long long>(f->number),
@ -2489,6 +2556,7 @@ Status DBImpl::DoCompactionWork(CompactionState* compact,
if (status.ok()) { if (status.ok()) {
status = InstallCompactionResults(compact); status = InstallCompactionResults(compact);
InstallSuperVersion(deletion_state);
} }
VersionSet::LevelSummaryStorage tmp; VersionSet::LevelSummaryStorage tmp;
Log(options_.info_log, Log(options_.info_log,
@ -2593,6 +2661,44 @@ Status DBImpl::Get(const ReadOptions& options,
return GetImpl(options, key, value); return GetImpl(options, key, value);
} }
// DeletionState gets created and destructed outside of the lock -- we
// use this convinently to:
// * malloc one SuperVersion() outside of the lock -- new_superversion
// * delete one SuperVersion() outside of the lock -- superversion_to_free
//
// However, if InstallSuperVersion() gets called twice with the same,
// deletion_state, we can't reuse the SuperVersion() that got malloced because
// first call already used it. In that rare case, we take a hit and create a
// new SuperVersion() inside of the mutex. We do similar thing
// for superversion_to_free
void DBImpl::InstallSuperVersion(DeletionState& deletion_state) {
// if new_superversion == nullptr, it means somebody already used it
SuperVersion* new_superversion =
(deletion_state.new_superversion != nullptr) ?
deletion_state.new_superversion : new SuperVersion();
SuperVersion* old_superversion = InstallSuperVersion(new_superversion);
deletion_state.new_superversion = nullptr;
if (deletion_state.superversion_to_free != nullptr) {
// somebody already put it there
delete old_superversion;
} else {
deletion_state.superversion_to_free = old_superversion;
}
}
DBImpl::SuperVersion* DBImpl::InstallSuperVersion(
SuperVersion* new_superversion) {
mutex_.AssertHeld();
new_superversion->Init(mem_, imm_, versions_->current());
SuperVersion* old_superversion = super_version_;
super_version_ = new_superversion;
if (old_superversion != nullptr && old_superversion->Unref()) {
old_superversion->Cleanup();
return old_superversion; // will let caller delete outside of mutex
}
return nullptr;
}
Status DBImpl::GetImpl(const ReadOptions& options, Status DBImpl::GetImpl(const ReadOptions& options,
const Slice& key, const Slice& key,
std::string* value, std::string* value,
@ -2601,27 +2707,20 @@ Status DBImpl::GetImpl(const ReadOptions& options,
StopWatch sw(env_, options_.statistics.get(), DB_GET); StopWatch sw(env_, options_.statistics.get(), DB_GET);
SequenceNumber snapshot; SequenceNumber snapshot;
std::vector<MemTable*> to_delete;
mutex_.Lock();
if (options.snapshot != nullptr) { if (options.snapshot != nullptr) {
snapshot = reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_; snapshot = reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_;
} else { } else {
snapshot = versions_->LastSequence(); snapshot = versions_->LastSequence();
} }
MemTable* mem = mem_; // This can be replaced by using atomics and spinlock instead of big mutex
MemTableList imm = imm_; mutex_.Lock();
Version* current = versions_->current(); SuperVersion* get_version = super_version_->Ref();
mem->Ref();
imm.RefAll();
current->Ref();
// Unlock while reading from files and memtables
mutex_.Unlock(); mutex_.Unlock();
bool have_stat_update = false; bool have_stat_update = false;
Version::GetStats stats; Version::GetStats stats;
// Prepare to store a list of merge operations if merge occurs. // Prepare to store a list of merge operations if merge occurs.
MergeContext merge_context; MergeContext merge_context;
@ -2629,32 +2728,41 @@ Status DBImpl::GetImpl(const ReadOptions& options,
// s is both in/out. When in, s could either be OK or MergeInProgress. // s is both in/out. When in, s could either be OK or MergeInProgress.
// merge_operands will contain the sequence of merges in the latter case. // merge_operands will contain the sequence of merges in the latter case.
LookupKey lkey(key, snapshot); LookupKey lkey(key, snapshot);
if (mem->Get(lkey, value, &s, merge_context, options_)) { if (get_version->mem->Get(lkey, value, &s, merge_context, options_)) {
// Done // Done
RecordTick(options_.statistics.get(), MEMTABLE_HIT); RecordTick(options_.statistics.get(), MEMTABLE_HIT);
} else if (imm.Get(lkey, value, &s, merge_context, options_)) { } else if (get_version->imm.Get(lkey, value, &s, merge_context, options_)) {
// Done // Done
RecordTick(options_.statistics.get(), MEMTABLE_HIT); RecordTick(options_.statistics.get(), MEMTABLE_HIT);
} else { } else {
current->Get(options, lkey, value, &s, &merge_context, &stats, get_version->current->Get(options, lkey, value, &s, &merge_context, &stats,
options_, value_found); options_, value_found);
have_stat_update = true; have_stat_update = true;
RecordTick(options_.statistics.get(), MEMTABLE_MISS); RecordTick(options_.statistics.get(), MEMTABLE_MISS);
} }
mutex_.Lock();
if (!options_.disable_seek_compaction && bool delete_get_version = false;
have_stat_update && current->UpdateStats(stats)) { if (!options_.disable_seek_compaction && have_stat_update) {
MaybeScheduleFlushOrCompaction(); mutex_.Lock();
if (get_version->current->UpdateStats(stats)) {
MaybeScheduleFlushOrCompaction();
}
if (get_version->Unref()) {
get_version->Cleanup();
delete_get_version = true;
}
mutex_.Unlock();
} else {
if (get_version->Unref()) {
mutex_.Lock();
get_version->Cleanup();
mutex_.Unlock();
delete_get_version = true;
}
}
if (delete_get_version) {
delete get_version;
} }
MemTable* m = mem->Unref();
imm.UnrefAll(&to_delete);
current->Unref();
mutex_.Unlock();
// free up all obsolete memtables outside the mutex
delete m;
for (MemTable* v: to_delete) delete v;
// Note, tickers are atomic now - no lock protection needed any more. // Note, tickers are atomic now - no lock protection needed any more.
RecordTick(options_.statistics.get(), NUMBER_KEYS_READ); RecordTick(options_.statistics.get(), NUMBER_KEYS_READ);
@ -2833,7 +2941,7 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
w.done = false; w.done = false;
StopWatch sw(env_, options_.statistics.get(), DB_WRITE); StopWatch sw(env_, options_.statistics.get(), DB_WRITE);
MutexLock l(&mutex_); mutex_.Lock();
writers_.push_back(&w); writers_.push_back(&w);
while (!w.done && &w != writers_.front()) { while (!w.done && &w != writers_.front()) {
w.cv.Wait(); w.cv.Wait();
@ -2844,6 +2952,7 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
} }
if (w.done) { if (w.done) {
mutex_.Unlock();
RecordTick(options_.statistics.get(), WRITE_DONE_BY_OTHER, 1); RecordTick(options_.statistics.get(), WRITE_DONE_BY_OTHER, 1);
return w.status; return w.status;
} else { } else {
@ -2851,7 +2960,8 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
} }
// May temporarily unlock and wait. // May temporarily unlock and wait.
Status status = MakeRoomForWrite(my_batch == nullptr); SuperVersion* superversion_to_free = nullptr;
Status status = MakeRoomForWrite(my_batch == nullptr, &superversion_to_free);
uint64_t last_sequence = versions_->LastSequence(); uint64_t last_sequence = versions_->LastSequence();
Writer* last_writer = &w; Writer* last_writer = &w;
if (status.ok() && my_batch != nullptr) { // nullptr batch is for compactions if (status.ok() && my_batch != nullptr) { // nullptr batch is for compactions
@ -2939,6 +3049,8 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
if (!writers_.empty()) { if (!writers_.empty()) {
writers_.front()->cv.Signal(); writers_.front()->cv.Signal();
} }
mutex_.Unlock();
delete superversion_to_free;
return status; return status;
} }
@ -3031,7 +3143,8 @@ uint64_t DBImpl::SlowdownAmount(int n, int top, int bottom) {
// REQUIRES: mutex_ is held // REQUIRES: mutex_ is held
// REQUIRES: this thread is currently at the front of the writer queue // REQUIRES: this thread is currently at the front of the writer queue
Status DBImpl::MakeRoomForWrite(bool force) { Status DBImpl::MakeRoomForWrite(bool force,
SuperVersion** superversion_to_free) {
mutex_.AssertHeld(); mutex_.AssertHeld();
assert(!writers_.empty()); assert(!writers_.empty());
bool allow_delay = !force; bool allow_delay = !force;
@ -3040,6 +3153,7 @@ Status DBImpl::MakeRoomForWrite(bool force) {
uint64_t rate_limit_delay_millis = 0; uint64_t rate_limit_delay_millis = 0;
Status s; Status s;
double score; double score;
*superversion_to_free = nullptr;
while (true) { while (true) {
if (!bg_error_.ok()) { if (!bg_error_.ok()) {
@ -3166,6 +3280,7 @@ Status DBImpl::MakeRoomForWrite(bool force) {
// Do this without holding the dbmutex lock. // Do this without holding the dbmutex lock.
assert(versions_->PrevLogNumber() == 0); assert(versions_->PrevLogNumber() == 0);
uint64_t new_log_number = versions_->NewFileNumber(); uint64_t new_log_number = versions_->NewFileNumber();
SuperVersion* new_superversion = nullptr;
mutex_.Unlock(); mutex_.Unlock();
{ {
EnvOptions soptions(storage_options_); EnvOptions soptions(storage_options_);
@ -3182,6 +3297,7 @@ Status DBImpl::MakeRoomForWrite(bool force) {
lfile->SetPreallocationBlockSize(1.1 * options_.write_buffer_size); lfile->SetPreallocationBlockSize(1.1 * options_.write_buffer_size);
memtmp = new MemTable( memtmp = new MemTable(
internal_comparator_, mem_rep_factory_, NumberLevels(), options_); internal_comparator_, mem_rep_factory_, NumberLevels(), options_);
new_superversion = new SuperVersion(options_.max_write_buffer_number);
} }
} }
mutex_.Lock(); mutex_.Lock();
@ -3206,6 +3322,7 @@ Status DBImpl::MakeRoomForWrite(bool force) {
mem_->SetLogNumber(logfile_number_); mem_->SetLogNumber(logfile_number_);
force = false; // Do not force another compaction if have room force = false; // Do not force another compaction if have room
MaybeScheduleFlushOrCompaction(); MaybeScheduleFlushOrCompaction();
*superversion_to_free = InstallSuperVersion(new_superversion);
} }
} }
return s; return s;
@ -3562,7 +3679,7 @@ Status DBImpl::DeleteFile(std::string name) {
FileMetaData metadata; FileMetaData metadata;
int maxlevel = NumberLevels(); int maxlevel = NumberLevels();
VersionEdit edit(maxlevel); VersionEdit edit(maxlevel);
DeletionState deletion_state; DeletionState deletion_state(0, true);
{ {
MutexLock l(&mutex_); MutexLock l(&mutex_);
status = versions_->GetMetadataForFile(number, &level, &metadata); status = versions_->GetMetadataForFile(number, &level, &metadata);
@ -3592,14 +3709,14 @@ Status DBImpl::DeleteFile(std::string name) {
} }
edit.DeleteFile(level, number); edit.DeleteFile(level, number);
status = versions_->LogAndApply(&edit, &mutex_); status = versions_->LogAndApply(&edit, &mutex_);
if (status.ok()) {
InstallSuperVersion(deletion_state);
}
FindObsoleteFiles(deletion_state, false); FindObsoleteFiles(deletion_state, false);
} // lock released here } // lock released here
LogFlush(options_.info_log); LogFlush(options_.info_log);
// remove files outside the db-lock
if (status.ok()) { PurgeObsoleteFiles(deletion_state);
// remove files outside the db-lock
PurgeObsoleteFiles(deletion_state);
}
return status; return status;
} }
@ -3712,6 +3829,7 @@ Status DB::Open(const Options& options, const std::string& dbname, DB** dbptr) {
s = impl->versions_->LogAndApply(&edit, &impl->mutex_); s = impl->versions_->LogAndApply(&edit, &impl->mutex_);
} }
if (s.ok()) { if (s.ok()) {
delete impl->InstallSuperVersion(new DBImpl::SuperVersion());
impl->mem_->SetLogNumber(impl->logfile_number_); impl->mem_->SetLogNumber(impl->logfile_number_);
impl->DeleteObsoleteFiles(); impl->DeleteObsoleteFiles();
impl->MaybeScheduleFlushOrCompaction(); impl->MaybeScheduleFlushOrCompaction();

View File

@ -105,7 +105,7 @@ class DBImpl : public DB {
virtual Status Flush(const FlushOptions& options, virtual Status Flush(const FlushOptions& options,
const ColumnFamilyHandle& column_family); const ColumnFamilyHandle& column_family);
virtual Status DisableFileDeletions(); virtual Status DisableFileDeletions();
virtual Status EnableFileDeletions(); virtual Status EnableFileDeletions(bool force);
// All the returned filenames start with "/" // All the returned filenames start with "/"
virtual Status GetLiveFiles(std::vector<std::string>&, virtual Status GetLiveFiles(std::vector<std::string>&,
uint64_t* manifest_file_size, uint64_t* manifest_file_size,
@ -160,12 +160,38 @@ class DBImpl : public DB {
default_interval_to_delete_obsolete_WAL_ = default_interval_to_delete_obsolete_WAL; default_interval_to_delete_obsolete_WAL_ = default_interval_to_delete_obsolete_WAL;
} }
// needed for CleanupIteratorState // holds references to memtable, all immutable memtables and version
struct SuperVersion {
MemTable* mem;
MemTableList imm;
Version* current;
std::atomic<uint32_t> refs;
// We need to_delete because during Cleanup(), imm.UnrefAll() returns
// all memtables that we need to free through this vector. We then
// delete all those memtables outside of mutex, during destruction
std::vector<MemTable*> to_delete;
// should be called outside the mutex
explicit SuperVersion(const int num_memtables = 0);
~SuperVersion();
SuperVersion* Ref();
// Returns true if this was the last reference and caller should
// call Clenaup() and delete the object
bool Unref();
// call these two methods with db mutex held
// Cleanup unrefs mem, imm and current. Also, it stores all memtables
// that needs to be deleted in to_delete vector. Unrefing those
// objects needs to be done in the mutex
void Cleanup();
void Init(MemTable* new_mem, const MemTableList& new_imm,
Version* new_current);
};
// needed for CleanupIteratorState
struct DeletionState { struct DeletionState {
inline bool HaveSomethingToDelete() const { inline bool HaveSomethingToDelete() const {
return memtables_to_free.size() || return all_files.size() ||
all_files.size() ||
sst_delete_files.size() || sst_delete_files.size() ||
log_delete_files.size(); log_delete_files.size();
} }
@ -187,15 +213,35 @@ class DBImpl : public DB {
// a list of memtables to be free // a list of memtables to be free
std::vector<MemTable *> memtables_to_free; std::vector<MemTable *> memtables_to_free;
SuperVersion* superversion_to_free; // if nullptr nothing to free
SuperVersion* new_superversion; // if nullptr no new superversion
// the current manifest_file_number, log_number and prev_log_number // the current manifest_file_number, log_number and prev_log_number
// that corresponds to the set of files in 'live'. // that corresponds to the set of files in 'live'.
uint64_t manifest_file_number, log_number, prev_log_number; uint64_t manifest_file_number, log_number, prev_log_number;
explicit DeletionState(const int num_memtables = 0) { explicit DeletionState(const int num_memtables = 0,
bool create_superversion = false) {
manifest_file_number = 0; manifest_file_number = 0;
log_number = 0; log_number = 0;
prev_log_number = 0; prev_log_number = 0;
memtables_to_free.reserve(num_memtables); memtables_to_free.reserve(num_memtables);
superversion_to_free = nullptr;
new_superversion =
create_superversion ? new SuperVersion(num_memtables) : nullptr;
}
~DeletionState() {
// free pending memtables
for (auto m : memtables_to_free) {
delete m;
}
// free superversion. if nullptr, this will be noop
delete superversion_to_free;
// if new_superversion was not used, it will be non-nullptr and needs
// to be freed here
delete new_superversion;
} }
}; };
@ -272,7 +318,11 @@ class DBImpl : public DB {
uint64_t* filenumber); uint64_t* filenumber);
uint64_t SlowdownAmount(int n, int top, int bottom); uint64_t SlowdownAmount(int n, int top, int bottom);
Status MakeRoomForWrite(bool force /* compact even if there is room? */); // MakeRoomForWrite will return superversion_to_free through an arugment,
// which the caller needs to delete. We do it because caller can delete
// the superversion outside of mutex
Status MakeRoomForWrite(bool force /* compact even if there is room? */,
SuperVersion** superversion_to_free);
WriteBatch* BuildBatchGroup(Writer** last_writer); WriteBatch* BuildBatchGroup(Writer** last_writer);
// Force current memtable contents to be flushed. // Force current memtable contents to be flushed.
@ -356,6 +406,8 @@ class DBImpl : public DB {
uint64_t logfile_number_; uint64_t logfile_number_;
unique_ptr<log::Writer> log_; unique_ptr<log::Writer> log_;
SuperVersion* super_version_;
std::string host_name_; std::string host_name_;
// Queue of writers. // Queue of writers.
@ -396,7 +448,12 @@ class DBImpl : public DB {
int64_t volatile last_log_ts; int64_t volatile last_log_ts;
// shall we disable deletion of obsolete files // shall we disable deletion of obsolete files
bool disable_delete_obsolete_files_; // if 0 the deletion is enabled.
// if non-zero, files will not be getting deleted
// This enables two different threads to call
// EnableFileDeletions() and DisableFileDeletions()
// without any synchronization
int disable_delete_obsolete_files_;
// last time when DeleteObsoleteFiles was invoked // last time when DeleteObsoleteFiles was invoked
uint64_t delete_obsolete_files_last_run_; uint64_t delete_obsolete_files_last_run_;
@ -523,6 +580,18 @@ class DBImpl : public DB {
std::vector<SequenceNumber>& snapshots, std::vector<SequenceNumber>& snapshots,
SequenceNumber* prev_snapshot); SequenceNumber* prev_snapshot);
// will return a pointer to SuperVersion* if previous SuperVersion
// if its reference count is zero and needs deletion or nullptr if not
// As argument takes a pointer to allocated SuperVersion
// Foreground threads call this function directly (they don't carry
// deletion state and have to handle their own creation and deletion
// of SuperVersion)
SuperVersion* InstallSuperVersion(SuperVersion* new_superversion);
// Background threads call this function, which is just a wrapper around
// the InstallSuperVersion() function above. Background threads carry
// deletion_state which can have new_superversion already allocated.
void InstallSuperVersion(DeletionState& deletion_state);
// Function that Get and KeyMayExist call with no_io true or false // Function that Get and KeyMayExist call with no_io true or false
// Note: 'value_found' from KeyMayExist propagates here // Note: 'value_found' from KeyMayExist propagates here
Status GetImpl(const ReadOptions& options, Status GetImpl(const ReadOptions& options,

View File

@ -74,7 +74,7 @@ public:
virtual Status DisableFileDeletions() { virtual Status DisableFileDeletions() {
return Status::NotSupported("Not supported operation in read only mode."); return Status::NotSupported("Not supported operation in read only mode.");
} }
virtual Status EnableFileDeletions() { virtual Status EnableFileDeletions(bool force) {
return Status::NotSupported("Not supported operation in read only mode."); return Status::NotSupported("Not supported operation in read only mode.");
} }
virtual Status GetLiveFiles(std::vector<std::string>&, virtual Status GetLiveFiles(std::vector<std::string>&,

View File

@ -2549,12 +2549,13 @@ class DeleteFilter : public CompactionFilter {
class ChangeFilter : public CompactionFilter { class ChangeFilter : public CompactionFilter {
public: public:
explicit ChangeFilter(int argv) : argv_(argv) {} explicit ChangeFilter(int argv) {
assert(argv == 100);
}
virtual bool Filter(int level, const Slice& key, virtual bool Filter(int level, const Slice& key,
const Slice& value, std::string* new_value, const Slice& value, std::string* new_value,
bool* value_changed) const override { bool* value_changed) const override {
assert(argv_ == 100);
assert(new_value != nullptr); assert(new_value != nullptr);
*new_value = NEW_VALUE; *new_value = NEW_VALUE;
*value_changed = true; *value_changed = true;
@ -2564,9 +2565,6 @@ class ChangeFilter : public CompactionFilter {
virtual const char* Name() const override { virtual const char* Name() const override {
return "ChangeFilter"; return "ChangeFilter";
} }
private:
const int argv_;
}; };
class KeepFilterFactory : public CompactionFilterFactory { class KeepFilterFactory : public CompactionFilterFactory {
@ -4491,7 +4489,7 @@ class ModelDB: public DB {
virtual Status DisableFileDeletions() { virtual Status DisableFileDeletions() {
return Status::OK(); return Status::OK();
} }
virtual Status EnableFileDeletions() { virtual Status EnableFileDeletions(bool force) {
return Status::OK(); return Status::OK();
} }
virtual Status GetLiveFiles(std::vector<std::string>&, uint64_t* size, virtual Status GetLiveFiles(std::vector<std::string>&, uint64_t* size,

View File

@ -225,7 +225,7 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
*s = Status::Corruption("Error: Could not perform merge."); *s = Status::Corruption("Error: Could not perform merge.");
} }
} else { } else {
*s = Status::NotFound(Slice()); *s = Status::NotFound();
} }
return true; return true;
} }

View File

@ -29,6 +29,11 @@ static void UnrefEntry(void* arg1, void* arg2) {
cache->Release(h); cache->Release(h);
} }
static Slice GetSliceForFileNumber(uint64_t file_number) {
return Slice(reinterpret_cast<const char*>(&file_number),
sizeof(file_number));
}
TableCache::TableCache(const std::string& dbname, TableCache::TableCache(const std::string& dbname,
const Options* options, const Options* options,
const EnvOptions& storage_options, const EnvOptions& storage_options,
@ -50,9 +55,7 @@ Status TableCache::FindTable(const EnvOptions& toptions,
Cache::Handle** handle, bool* table_io, Cache::Handle** handle, bool* table_io,
const bool no_io) { const bool no_io) {
Status s; Status s;
char buf[sizeof(file_number)]; Slice key = GetSliceForFileNumber(file_number);
EncodeFixed64(buf, file_number);
Slice key(buf, sizeof(buf));
*handle = cache_->Lookup(key); *handle = cache_->Lookup(key);
if (*handle == nullptr) { if (*handle == nullptr) {
if (no_io) { // Dont do IO and return a not-found status if (no_io) { // Dont do IO and return a not-found status
@ -165,9 +168,7 @@ bool TableCache::PrefixMayMatch(const ReadOptions& options,
} }
void TableCache::Evict(uint64_t file_number) { void TableCache::Evict(uint64_t file_number) {
char buf[sizeof(file_number)]; cache_->Erase(GetSliceForFileNumber(file_number));
EncodeFixed64(buf, file_number);
cache_->Erase(Slice(buf, sizeof(buf)));
} }
} // namespace rocksdb } // namespace rocksdb

View File

@ -545,7 +545,7 @@ void Version::Get(const ReadOptions& options,
case kFound: case kFound:
return; return;
case kDeleted: case kDeleted:
*status = Status::NotFound(Slice()); // Use empty error message for speed *status = Status::NotFound(); // Use empty error message for speed
return; return;
case kCorrupt: case kCorrupt:
*status = Status::Corruption("corrupted key for ", user_key); *status = Status::Corruption("corrupted key for ", user_key);
@ -570,7 +570,7 @@ void Version::Get(const ReadOptions& options,
user_key); user_key);
} }
} else { } else {
*status = Status::NotFound(Slice()); // Use an empty error message for speed *status = Status::NotFound(); // Use an empty error message for speed
} }
} }

View File

@ -272,12 +272,14 @@ class VersionSet {
int64_t NumLevelBytes(int level) const; int64_t NumLevelBytes(int level) const;
// Return the last sequence number. // Return the last sequence number.
uint64_t LastSequence() const { return last_sequence_; } uint64_t LastSequence() const {
return last_sequence_.load(std::memory_order_acquire);
}
// Set the last sequence number to s. // Set the last sequence number to s.
void SetLastSequence(uint64_t s) { void SetLastSequence(uint64_t s) {
assert(s >= last_sequence_); assert(s >= last_sequence_);
last_sequence_ = s; last_sequence_.store(s, std::memory_order_release);
} }
// Mark the specified file number as used. // Mark the specified file number as used.
@ -476,7 +478,7 @@ class VersionSet {
const InternalKeyComparator icmp_; const InternalKeyComparator icmp_;
uint64_t next_file_number_; uint64_t next_file_number_;
uint64_t manifest_file_number_; uint64_t manifest_file_number_;
uint64_t last_sequence_; std::atomic<uint64_t> last_sequence_;
uint64_t log_number_; uint64_t log_number_;
uint64_t prev_log_number_; // 0 or backing store for memtable being compacted uint64_t prev_log_number_; // 0 or backing store for memtable being compacted

View File

@ -363,7 +363,15 @@ class DB {
virtual Status DisableFileDeletions() = 0; virtual Status DisableFileDeletions() = 0;
// Allow compactions to delete obselete files. // Allow compactions to delete obselete files.
virtual Status EnableFileDeletions() = 0; // If force == true, the call to EnableFileDeletions() will guarantee that
// file deletions are enabled after the call, even if DisableFileDeletions()
// was called multiple times before.
// If force == false, EnableFileDeletions will only enable file deletion
// after it's been called at least as many times as DisableFileDeletions(),
// enabling the two methods to be called by two threads concurrently without
// synchronization -- i.e., file deletions will be enabled only after both
// threads call EnableFileDeletions()
virtual Status EnableFileDeletions(bool force = true) = 0;
// GetLiveFiles followed by GetSortedWalFiles can generate a lossless backup // GetLiveFiles followed by GetSortedWalFiles can generate a lossless backup

View File

@ -25,7 +25,7 @@ namespace rocksdb {
class Status { class Status {
public: public:
// Create a success status. // Create a success status.
Status() : state_(nullptr) { } Status() : code_(kOk), state_(nullptr) { }
~Status() { delete[] state_; } ~Status() { delete[] state_; }
// Copy the specified status. // Copy the specified status.
@ -39,6 +39,10 @@ class Status {
static Status NotFound(const Slice& msg, const Slice& msg2 = Slice()) { static Status NotFound(const Slice& msg, const Slice& msg2 = Slice()) {
return Status(kNotFound, msg, msg2); return Status(kNotFound, msg, msg2);
} }
// Fast path for not found without malloc;
static Status NotFound() {
return Status(kNotFound);
}
static Status Corruption(const Slice& msg, const Slice& msg2 = Slice()) { static Status Corruption(const Slice& msg, const Slice& msg2 = Slice()) {
return Status(kCorruption, msg, msg2); return Status(kCorruption, msg, msg2);
} }
@ -59,7 +63,7 @@ class Status {
} }
// Returns true iff the status indicates success. // Returns true iff the status indicates success.
bool ok() const { return (state_ == nullptr); } bool ok() const { return code() == kOk; }
// Returns true iff the status indicates a NotFound error. // Returns true iff the status indicates a NotFound error.
bool IsNotFound() const { return code() == kNotFound; } bool IsNotFound() const { return code() == kNotFound; }
@ -87,13 +91,6 @@ class Status {
std::string ToString() const; std::string ToString() const;
private: private:
// OK status has a nullptr state_. Otherwise, state_ is a new[] array
// of the following form:
// state_[0..3] == length of message
// state_[4] == code
// state_[5..] == message
const char* state_;
enum Code { enum Code {
kOk = 0, kOk = 0,
kNotFound = 1, kNotFound = 1,
@ -105,20 +102,30 @@ class Status {
kIncomplete = 7 kIncomplete = 7
}; };
Code code() const { // A nullptr state_ (which is always the case for OK) means the message
return (state_ == nullptr) ? kOk : static_cast<Code>(state_[4]); // is empty.
} // of the following form:
// state_[0..3] == length of message
// state_[4..] == message
Code code_;
const char* state_;
Code code() const {
return code_;
}
explicit Status(Code code) : code_(code), state_(nullptr) { }
Status(Code code, const Slice& msg, const Slice& msg2); Status(Code code, const Slice& msg, const Slice& msg2);
static const char* CopyState(const char* s); static const char* CopyState(const char* s);
}; };
inline Status::Status(const Status& s) { inline Status::Status(const Status& s) {
code_ = s.code_;
state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_); state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_);
} }
inline void Status::operator=(const Status& s) { inline void Status::operator=(const Status& s) {
// The following condition catches both aliasing (when this == &s), // The following condition catches both aliasing (when this == &s),
// and the common case where both s and *this are ok. // and the common case where both s and *this are ok.
code_ = s.code_;
if (state_ != s.state_) { if (state_ != s.state_) {
delete[] state_; delete[] state_;
state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_); state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_);

View File

@ -56,7 +56,7 @@ class LogFile {
}; };
struct BatchResult { struct BatchResult {
SequenceNumber sequence = SequenceNumber(); SequenceNumber sequence = 0;
std::unique_ptr<WriteBatch> writeBatchPtr; std::unique_ptr<WriteBatch> writeBatchPtr;
}; };

View File

@ -158,8 +158,8 @@ class StackableDB : public DB {
return db_->DisableFileDeletions(); return db_->DisableFileDeletions();
} }
virtual Status EnableFileDeletions() override { virtual Status EnableFileDeletions(bool force) override {
return db_->EnableFileDeletions(); return db_->EnableFileDeletions(force);
} }
virtual Status GetLiveFiles(std::vector<std::string>& vec, uint64_t* mfs, virtual Status GetLiveFiles(std::vector<std::string>& vec, uint64_t* mfs,

329
util/autovector.h Normal file
View File

@ -0,0 +1,329 @@
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
#pragma once
#include <algorithm>
#include <cassert>
#include <stdexcept>
#include <iterator>
#include <vector>
namespace rocksdb {
// A vector that leverages pre-allocated stack-based array to achieve better
// performance for array with small amount of items.
//
// The interface resembles that of vector, but with less features since we aim
// to solve the problem that we have in hand, rather than implementing a
// full-fledged generic container.
//
// Currently we don't support:
// * reserve()/shrink_to_fit()/resize()
// If used correctly, in most cases, people should not touch the
// underlying vector at all.
// * random insert()/erase(), please only use push_back()/pop_back().
// * No move/swap operations. Each autovector instance has a
// stack-allocated array and if we want support move/swap operations, we
// need to copy the arrays other than just swapping the pointers. In this
// case we'll just explicitly forbid these operations since they may
// lead users to make false assumption by thinking they are inexpensive
// operations.
//
// Naming style of public methods almost follows that of the STL's.
template <class T, size_t kSize = 8>
class autovector {
public:
// General STL-style container member types.
typedef T value_type;
typedef typename std::vector<T>::difference_type difference_type;
typedef typename std::vector<T>::size_type size_type;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef value_type* pointer;
typedef const value_type* const_pointer;
// This class is the base for regular/const iterator
template <class TAutoVector, class TValueType>
class iterator_impl {
public:
// -- iterator traits
typedef iterator_impl<TAutoVector, TValueType> self_type;
typedef TValueType value_type;
typedef TValueType& reference;
typedef TValueType* pointer;
typedef typename TAutoVector::difference_type difference_type;
typedef std::random_access_iterator_tag iterator_category;
iterator_impl(TAutoVector* vect, size_t index)
: vect_(vect)
, index_(index) {
};
iterator_impl(const iterator_impl&) = default;
~iterator_impl() { }
iterator_impl& operator=(const iterator_impl&) = default;
// -- Advancement
// iterator++
self_type& operator++() {
++index_;
return *this;
}
// ++iterator
self_type operator++(int) {
auto old = *this;
++index_;
return old;
}
// iterator--
self_type& operator--() {
--index_;
return *this;
}
// --iterator
self_type operator--(int) {
auto old = *this;
--index_;
return old;
}
self_type operator-(difference_type len) {
return self_type(vect_, index_ - len);
}
difference_type operator-(const self_type& other) {
assert(vect_ == other.vect_);
return index_ - other.index_;
}
self_type operator+(difference_type len) {
return self_type(vect_, index_ + len);
}
self_type& operator+=(difference_type len) {
index_ += len;
return *this;
}
self_type& operator-=(difference_type len) {
index_ -= len;
return *this;
}
// -- Reference
reference operator*() {
assert(vect_->size() >= index_);
return (*vect_)[index_];
}
pointer operator->() {
assert(vect_->size() >= index_);
return &(*vect_)[index_];
}
// -- Logical Operators
bool operator==(const self_type& other) const {
assert(vect_ == other.vect_);
return index_ == other.index_;
}
bool operator!=(const self_type& other) const {
return !(*this == other);
}
bool operator>(const self_type& other) const {
assert(vect_ == other.vect_);
return index_ > other.index_;
}
bool operator<(const self_type& other) const {
assert(vect_ == other.vect_);
return index_ < other.index_;
}
bool operator>=(const self_type& other) const {
assert(vect_ == other.vect_);
return index_ >= other.index_;
}
bool operator<=(const self_type& other) const {
assert(vect_ == other.vect_);
return index_ <= other.index_;
}
private:
TAutoVector* vect_ = nullptr;
size_t index_ = 0;
};
typedef iterator_impl<autovector, value_type> iterator;
typedef iterator_impl<const autovector, const value_type> const_iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
autovector() = default;
~autovector() = default;
// -- Immutable operations
// Indicate if all data resides in in-stack data structure.
bool only_in_stack() const {
// If no element was inserted at all, the vector's capacity will be `0`.
return vect_.capacity() == 0;
}
size_type size() const {
return num_stack_items_ + vect_.size();
}
bool empty() const {
return size() == 0;
}
// will not check boundry
const_reference operator[](size_type n) const {
return n < kSize ? values_[n] : vect_[n - kSize];
}
reference operator[](size_type n) {
return n < kSize ? values_[n] : vect_[n - kSize];
}
// will check boundry
const_reference at(size_type n) const {
if (n >= size()) {
throw std::out_of_range("autovector: index out of range");
}
return (*this)[n];
}
reference at(size_type n) {
if (n >= size()) {
throw std::out_of_range("autovector: index out of range");
}
return (*this)[n];
}
reference front() {
assert(!empty());
return *begin();
}
const_reference front() const {
assert(!empty());
return *begin();
}
reference back() {
assert(!empty());
return *(end() - 1);
}
const_reference back() const {
assert(!empty());
return *(end() - 1);
}
// -- Mutable Operations
void push_back(T&& item) {
if (num_stack_items_ < kSize) {
values_[num_stack_items_++] = std::move(item);
} else {
vect_.push_back(item);
}
}
void push_back(const T& item) {
push_back(value_type(item));
}
template<class... Args>
void emplace_back(Args&&... args) {
push_back(value_type(args...));
}
void pop_back() {
assert(!empty());
if (!vect_.empty()) {
vect_.pop_back();
} else {
--num_stack_items_;
}
}
void clear() {
num_stack_items_ = 0;
vect_.clear();
}
// -- Copy and Assignment
autovector& assign(const autovector& other);
autovector(const autovector& other) {
assign(other);
}
autovector& operator=(const autovector& other) {
return assign(other);
}
// move operation are disallowed since it is very hard to make sure both
// autovectors are allocated from the same function stack.
autovector& operator=(autovector&& other) = delete;
autovector(autovector&& other) = delete;
// -- Iterator Operations
iterator begin() {
return iterator(this, 0);
}
const_iterator begin() const {
return const_iterator(this, 0);
}
iterator end() {
return iterator(this, this->size());
}
const_iterator end() const {
return const_iterator(this, this->size());
}
reverse_iterator rbegin() {
return reverse_iterator(end());
}
const_reverse_iterator rbegin() const {
return const_reverse_iterator(end());
}
reverse_iterator rend() {
return reverse_iterator(begin());
}
const_reverse_iterator rend() const {
return const_reverse_iterator(begin());
}
private:
size_type num_stack_items_ = 0; // current number of items
value_type values_[kSize]; // the first `kSize` items
// used only if there are more than `kSize` items.
std::vector<T> vect_;
};
template <class T, size_t kSize>
autovector<T, kSize>& autovector<T, kSize>::assign(const autovector& other) {
// copy the internal vector
vect_.assign(other.vect_.begin(), other.vect_.end());
// copy array
num_stack_items_ = other.num_stack_items_;
std::copy(other.values_, other.values_ + num_stack_items_, values_);
return *this;
}
} // rocksdb

290
util/autovector_test.cc Normal file
View File

@ -0,0 +1,290 @@
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
#include <atomic>
#include <iostream>
#include "rocksdb/env.h"
#include "util/autovector.h"
#include "util/testharness.h"
#include "util/testutil.h"
namespace rocksdb {
using namespace std;
class AutoVectorTest { };
const size_t kSize = 8;
TEST(AutoVectorTest, PushBackAndPopBack) {
autovector<size_t, kSize> vec;
ASSERT_TRUE(vec.empty());
ASSERT_EQ(0ul, vec.size());
for (size_t i = 0; i < 1000 * kSize; ++i) {
vec.push_back(i);
ASSERT_TRUE(!vec.empty());
if (i < kSize) {
ASSERT_TRUE(vec.only_in_stack());
} else {
ASSERT_TRUE(!vec.only_in_stack());
}
ASSERT_EQ(i + 1, vec.size());
ASSERT_EQ(i, vec[i]);
ASSERT_EQ(i, vec.at(i));
}
size_t size = vec.size();
while (size != 0) {
vec.pop_back();
// will always be in heap
ASSERT_TRUE(!vec.only_in_stack());
ASSERT_EQ(--size, vec.size());
}
ASSERT_TRUE(vec.empty());
}
TEST(AutoVectorTest, EmplaceBack) {
typedef std::pair<size_t, std::string> ValueType;
autovector<ValueType, kSize> vec;
for (size_t i = 0; i < 1000 * kSize; ++i) {
vec.emplace_back(i, std::to_string(i + 123));
ASSERT_TRUE(!vec.empty());
if (i < kSize) {
ASSERT_TRUE(vec.only_in_stack());
} else {
ASSERT_TRUE(!vec.only_in_stack());
}
ASSERT_EQ(i + 1, vec.size());
ASSERT_EQ(i, vec[i].first);
ASSERT_EQ(std::to_string(i + 123), vec[i].second);
}
vec.clear();
ASSERT_TRUE(vec.empty());
ASSERT_TRUE(!vec.only_in_stack());
}
void AssertEqual(
const autovector<size_t, kSize>& a, const autovector<size_t, kSize>& b) {
ASSERT_EQ(a.size(), b.size());
ASSERT_EQ(a.empty(), b.empty());
ASSERT_EQ(a.only_in_stack(), b.only_in_stack());
for (size_t i = 0; i < a.size(); ++i) {
ASSERT_EQ(a[i], b[i]);
}
}
TEST(AutoVectorTest, CopyAndAssignment) {
// Test both heap-allocated and stack-allocated cases.
for (auto size : { kSize / 2, kSize * 1000 }) {
autovector<size_t, kSize> vec;
for (size_t i = 0; i < size; ++i) {
vec.push_back(i);
}
{
autovector<size_t, kSize> other;
other = vec;
AssertEqual(other, vec);
}
{
autovector<size_t, kSize> other(vec);
AssertEqual(other, vec);
}
}
}
TEST(AutoVectorTest, Iterators) {
autovector<std::string, kSize> vec;
for (size_t i = 0; i < kSize * 1000; ++i) {
vec.push_back(std::to_string(i));
}
// basic operator test
ASSERT_EQ(vec.front(), *vec.begin());
ASSERT_EQ(vec.back(), *(vec.end() - 1));
ASSERT_TRUE(vec.begin() < vec.end());
// non-const iterator
size_t index = 0;
for (const auto& item : vec) {
ASSERT_EQ(vec[index++], item);
}
index = vec.size() - 1;
for (auto pos = vec.rbegin(); pos != vec.rend(); ++pos) {
ASSERT_EQ(vec[index--], *pos);
}
// const iterator
const auto& cvec = vec;
index = 0;
for (const auto& item : cvec) {
ASSERT_EQ(cvec[index++], item);
}
index = vec.size() - 1;
for (auto pos = cvec.rbegin(); pos != cvec.rend(); ++pos) {
ASSERT_EQ(cvec[index--], *pos);
}
// forward and backward
auto pos = vec.begin();
while (pos != vec.end()) {
auto old_val = *pos;
auto old = pos++;
// HACK: make sure -> works
ASSERT_TRUE(!old->empty());
ASSERT_EQ(old_val, *old);
ASSERT_TRUE(pos == vec.end() || old_val != *pos);
}
pos = vec.begin();
for (size_t i = 0; i < vec.size(); i += 2) {
// Cannot use ASSERT_EQ since that macro depends on iostream serialization
ASSERT_TRUE(pos + 2 - 2 == pos);
pos += 2;
ASSERT_TRUE(pos >= vec.begin());
ASSERT_TRUE(pos <= vec.end());
size_t diff = static_cast<size_t>(pos - vec.begin());
ASSERT_EQ(i + 2, diff);
}
}
vector<string> GetTestKeys(size_t size) {
vector<string> keys;
keys.resize(size);
int index = 0;
for (auto& key : keys) {
key = "item-" + to_string(index++);
}
return keys;
}
template<class TVector>
void BenchmarkVectorCreationAndInsertion(
string name, size_t ops, size_t item_size,
const std::vector<typename TVector::value_type>& items) {
auto env = Env::Default();
int index = 0;
auto start_time = env->NowNanos();
auto ops_remaining = ops;
while(ops_remaining--) {
TVector v;
for (size_t i = 0; i < item_size; ++i) {
v.push_back(items[index++]);
}
}
auto elapsed = env->NowNanos() - start_time;
cout << "created " << ops << " " << name << " instances:\n\t"
<< "each was inserted with " << item_size << " elements\n\t"
<< "total time elapsed: " << elapsed << " (ns)" << endl;
}
template <class TVector>
size_t BenchmarkSequenceAccess(string name, size_t ops, size_t elem_size) {
TVector v;
for (const auto& item : GetTestKeys(elem_size)) {
v.push_back(item);
}
auto env = Env::Default();
auto ops_remaining = ops;
auto start_time = env->NowNanos();
size_t total = 0;
while (ops_remaining--) {
auto end = v.end();
for (auto pos = v.begin(); pos != end; ++pos) {
total += pos->size();
}
}
auto elapsed = env->NowNanos() - start_time;
cout << "performed " << ops << " sequence access against " << name << "\n\t"
<< "size: " << elem_size << "\n\t"
<< "total time elapsed: " << elapsed << " (ns)" << endl;
// HACK avoid compiler's optimization to ignore total
return total;
}
// This test case only reports the performance between std::vector<string>
// and autovector<string>. We chose string for comparison because in most
// o our use cases we used std::vector<string>.
TEST(AutoVectorTest, PerfBench) {
// We run same operations for kOps times in order to get a more fair result.
size_t kOps = 100000;
// Creation and insertion test
// Test the case when there is:
// * no element inserted: internal array of std::vector may not really get
// initialize.
// * one element inserted: internal array of std::vector must have
// initialized.
// * kSize elements inserted. This shows the most time we'll spend if we
// keep everything in stack.
// * 2 * kSize elements inserted. The internal vector of
// autovector must have been initialized.
cout << "=====================================================" << endl;
cout << "Creation and Insertion Test (value type: std::string)" << endl;
cout << "=====================================================" << endl;
// pre-generated unique keys
auto string_keys = GetTestKeys(kOps * 2 * kSize);
for (auto insertions : { 0ul, 1ul, kSize / 2, kSize, 2 * kSize }) {
BenchmarkVectorCreationAndInsertion<vector<string>>(
"vector<string>", kOps, insertions, string_keys
);
BenchmarkVectorCreationAndInsertion<autovector<string, kSize>>(
"autovector<string>", kOps, insertions, string_keys
);
cout << "-----------------------------------" << endl;
}
cout << "=====================================================" << endl;
cout << "Creation and Insertion Test (value type: uint64_t)" << endl;
cout << "=====================================================" << endl;
// pre-generated unique keys
vector<uint64_t> int_keys(kOps * 2 * kSize);
for (size_t i = 0; i < kOps * 2 * kSize; ++i) {
int_keys[i] = i;
}
for (auto insertions : { 0ul, 1ul, kSize / 2, kSize, 2 * kSize }) {
BenchmarkVectorCreationAndInsertion<vector<uint64_t>>(
"vector<uint64_t>", kOps, insertions, int_keys
);
BenchmarkVectorCreationAndInsertion<autovector<uint64_t, kSize>>(
"autovector<uint64_t>", kOps, insertions, int_keys
);
cout << "-----------------------------------" << endl;
}
// Sequence Access Test
cout << "=====================================================" << endl;
cout << "Sequence Access Test" << endl;
cout << "=====================================================" << endl;
for (auto elem_size : { kSize / 2, kSize, 2 * kSize }) {
BenchmarkSequenceAccess<vector<string>>(
"vector", kOps, elem_size
);
BenchmarkSequenceAccess<autovector<string, kSize>>(
"autovector", kOps, elem_size
);
cout << "-----------------------------------" << endl;
}
}
} // namespace rocksdb
int main(int argc, char** argv) {
return rocksdb::test::RunAllTests();
}

View File

@ -389,7 +389,7 @@ class PosixMmapFile : public WritableFile {
} }
Status MapNewRegion() { Status MapNewRegion() {
#ifdef OS_LINUX #ifdef ROCKSDB_FALLOCATE_PRESENT
assert(base_ == nullptr); assert(base_ == nullptr);
TEST_KILL_RANDOM(rocksdb_kill_odds); TEST_KILL_RANDOM(rocksdb_kill_odds);
@ -575,7 +575,7 @@ class PosixMmapFile : public WritableFile {
#endif #endif
} }
#ifdef OS_LINUX #ifdef ROCKSDB_FALLOCATE_PRESENT
virtual Status Allocate(off_t offset, off_t len) { virtual Status Allocate(off_t offset, off_t len) {
TEST_KILL_RANDOM(rocksdb_kill_odds); TEST_KILL_RANDOM(rocksdb_kill_odds);
if (!fallocate(fd_, FALLOC_FL_KEEP_SIZE, offset, len)) { if (!fallocate(fd_, FALLOC_FL_KEEP_SIZE, offset, len)) {
@ -752,7 +752,7 @@ class PosixWritableFile : public WritableFile {
#endif #endif
} }
#ifdef OS_LINUX #ifdef ROCKSDB_FALLOCATE_PRESENT
virtual Status Allocate(off_t offset, off_t len) { virtual Status Allocate(off_t offset, off_t len) {
TEST_KILL_RANDOM(rocksdb_kill_odds); TEST_KILL_RANDOM(rocksdb_kill_odds);
if (!fallocate(fd_, FALLOC_FL_KEEP_SIZE, offset, len)) { if (!fallocate(fd_, FALLOC_FL_KEEP_SIZE, offset, len)) {
@ -856,7 +856,7 @@ class PosixRandomRWFile : public RandomRWFile {
return Status::OK(); return Status::OK();
} }
#ifdef OS_LINUX #ifdef ROCKSDB_FALLOCATE_PRESENT
virtual Status Allocate(off_t offset, off_t len) { virtual Status Allocate(off_t offset, off_t len) {
if (!fallocate(fd_, FALLOC_FL_KEEP_SIZE, offset, len)) { if (!fallocate(fd_, FALLOC_FL_KEEP_SIZE, offset, len)) {
return Status::OK(); return Status::OK();
@ -1297,7 +1297,7 @@ class PosixEnv : public Env {
} }
bool SupportsFastAllocate(const std::string& path) { bool SupportsFastAllocate(const std::string& path) {
#ifdef OS_LINUX #ifdef ROCKSDB_FALLOCATE_PRESENT
struct statfs s; struct statfs s;
if (statfs(path.c_str(), &s)){ if (statfs(path.c_str(), &s)){
return false; return false;

View File

@ -111,7 +111,7 @@ class PosixLogger : public Logger {
assert(p <= limit); assert(p <= limit);
const size_t write_size = p - base; const size_t write_size = p - base;
#ifdef OS_LINUX #ifdef ROCKSDB_FALLOCATE_PRESENT
// If this write would cross a boundary of kDebugLogChunkSize // If this write would cross a boundary of kDebugLogChunkSize
// space, pre-allocate more space to avoid overly large // space, pre-allocate more space to avoid overly large
// allocations from filesystem allocsize options. // allocations from filesystem allocsize options.

View File

@ -16,68 +16,65 @@ namespace rocksdb {
const char* Status::CopyState(const char* state) { const char* Status::CopyState(const char* state) {
uint32_t size; uint32_t size;
memcpy(&size, state, sizeof(size)); memcpy(&size, state, sizeof(size));
char* result = new char[size + 5]; char* result = new char[size + 4];
memcpy(result, state, size + 5); memcpy(result, state, size + 4);
return result; return result;
} }
Status::Status(Code code, const Slice& msg, const Slice& msg2) { Status::Status(Code code, const Slice& msg, const Slice& msg2) :
code_(code) {
assert(code != kOk); assert(code != kOk);
const uint32_t len1 = msg.size(); const uint32_t len1 = msg.size();
const uint32_t len2 = msg2.size(); const uint32_t len2 = msg2.size();
const uint32_t size = len1 + (len2 ? (2 + len2) : 0); const uint32_t size = len1 + (len2 ? (2 + len2) : 0);
char* result = new char[size + 5]; char* result = new char[size + 4];
memcpy(result, &size, sizeof(size)); memcpy(result, &size, sizeof(size));
result[4] = static_cast<char>(code); memcpy(result + 4, msg.data(), len1);
memcpy(result + 5, msg.data(), len1);
if (len2) { if (len2) {
result[5 + len1] = ':'; result[4 + len1] = ':';
result[6 + len1] = ' '; result[5 + len1] = ' ';
memcpy(result + 7 + len1, msg2.data(), len2); memcpy(result + 6 + len1, msg2.data(), len2);
} }
state_ = result; state_ = result;
} }
std::string Status::ToString() const { std::string Status::ToString() const {
if (state_ == nullptr) { char tmp[30];
return "OK"; const char* type;
} else { switch (code_) {
char tmp[30]; case kOk:
const char* type; return "OK";
switch (code()) { case kNotFound:
case kOk: type = "NotFound: ";
type = "OK"; break;
break; case kCorruption:
case kNotFound: type = "Corruption: ";
type = "NotFound: "; break;
break; case kNotSupported:
case kCorruption: type = "Not implemented: ";
type = "Corruption: "; break;
break; case kInvalidArgument:
case kNotSupported: type = "Invalid argument: ";
type = "Not implemented: "; break;
break; case kIOError:
case kInvalidArgument: type = "IO error: ";
type = "Invalid argument: "; break;
break; case kMergeInProgress:
case kIOError: type = "Merge In Progress: ";
type = "IO error: "; break;
break; default:
case kMergeInProgress: snprintf(tmp, sizeof(tmp), "Unknown code(%d): ",
type = "Merge In Progress: "; static_cast<int>(code()));
break; type = tmp;
default: break;
snprintf(tmp, sizeof(tmp), "Unknown code(%d): ", }
static_cast<int>(code())); std::string result(type);
type = tmp; if (state_ != nullptr) {
break;
}
std::string result(type);
uint32_t length; uint32_t length;
memcpy(&length, state_, sizeof(length)); memcpy(&length, state_, sizeof(length));
result.append(state_ + 5, length); result.append(state_ + 4, length);
return result;
} }
return result;
} }
} // namespace rocksdb } // namespace rocksdb

View File

@ -50,7 +50,7 @@ class DummyDB : public StackableDB {
return options_; return options_;
} }
virtual Status EnableFileDeletions() override { virtual Status EnableFileDeletions(bool force) override {
ASSERT_TRUE(!deletions_enabled_); ASSERT_TRUE(!deletions_enabled_);
deletions_enabled_ = true; deletions_enabled_ = true;
return Status::OK(); return Status::OK();