Add utility class Defer (#6382)

Summary:
Add a utility class `Defer` to defer the execution of a function until the Defer object goes out of scope.
Used in VersionSet:: ProcessManifestWrites as an example.
The inline comments for class `Defer` have more details.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/6382

Test Plan: `make defer_test version_set_test && ./defer_test && ./version_set_test`

Differential Revision: D19797538

Pulled By: cheng-chang

fbshipit-source-id: b1a9b7306e4fd4f48ec2ab55783caa561a315f0f
This commit is contained in:
Cheng Chang 2020-02-10 17:58:03 -08:00 committed by Facebook Github Bot
parent cbf5f3be43
commit dafb568052
7 changed files with 115 additions and 14 deletions

View File

@ -1026,6 +1026,7 @@ if(WITH_TESTS)
util/bloom_test.cc
util/coding_test.cc
util/crc32c_test.cc
util/defer_test.cc
util/dynamic_bloom_test.cc
util/file_reader_writer_test.cc
util/filelock_test.cc

View File

@ -596,6 +596,7 @@ TESTS = \
db_secondary_test \
block_cache_tracer_test \
block_cache_trace_analyzer_test \
defer_test \
ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
TESTS += folly_synchronization_distributed_mutex_test
@ -1714,6 +1715,9 @@ block_cache_tracer_test: trace_replay/block_cache_tracer_test.o trace_replay/blo
block_cache_trace_analyzer_test: tools/block_cache_analyzer/block_cache_trace_analyzer_test.o tools/block_cache_analyzer/block_cache_trace_analyzer.o $(LIBOBJECTS) $(TESTHARNESS)
$(AM_LINK)
defer_test: util/defer_test.o $(LIBOBJECTS) $(TESTHARNESS)
$(AM_LINK)
#-------------------------------------------------
# make install related stuff
INSTALL_PATH ?= /usr/local

View File

@ -904,6 +904,13 @@ ROCKS_TESTS = [
[],
[],
],
[
"defer_test",
"util/defer_test.cc",
"serial",
[],
[],
],
[
"delete_scheduler_test",
"file/delete_scheduler_test.cc",

View File

@ -50,6 +50,7 @@
#include "table/two_level_iterator.h"
#include "test_util/sync_point.h"
#include "util/coding.h"
#include "util/defer.h"
#include "util/stop_watch.h"
#include "util/string_util.h"
#include "util/user_comparator_wrapper.h"
@ -3604,6 +3605,14 @@ Status VersionSet::ProcessManifestWrites(
autovector<Version*> versions;
autovector<const MutableCFOptions*> mutable_cf_options_ptrs;
std::vector<std::unique_ptr<BaseReferencedVersionBuilder>> builder_guards;
Status s;
Defer defer([&s, &versions]() {
if (!s.ok()) {
for (auto v : versions) {
delete v;
}
}
});
if (first_writer.edit_list.front()->IsColumnFamilyManipulation()) {
// No group commits for column family add or drop
@ -3689,12 +3698,8 @@ Status VersionSet::ProcessManifestWrites(
} else if (group_start != std::numeric_limits<size_t>::max()) {
group_start = std::numeric_limits<size_t>::max();
}
Status s = LogAndApplyHelper(last_writer->cfd, builder, e, mu);
s = LogAndApplyHelper(last_writer->cfd, builder, e, mu);
if (!s.ok()) {
// free up the allocated memory
for (auto v : versions) {
delete v;
}
return s;
}
batch_edits.push_back(e);
@ -3704,12 +3709,8 @@ Status VersionSet::ProcessManifestWrites(
assert(!builder_guards.empty() &&
builder_guards.size() == versions.size());
auto* builder = builder_guards[i]->version_builder();
Status s = builder->SaveTo(versions[i]->storage_info());
s = builder->SaveTo(versions[i]->storage_info());
if (!s.ok()) {
// free up the allocated memory
for (auto v : versions) {
delete v;
}
return s;
}
}
@ -3752,7 +3753,6 @@ Status VersionSet::ProcessManifestWrites(
#endif // NDEBUG
uint64_t new_manifest_file_size = 0;
Status s;
assert(pending_manifest_file_number_ == 0);
if (!descriptor_log_ ||
@ -3959,9 +3959,6 @@ Status VersionSet::ProcessManifestWrites(
ROCKS_LOG_ERROR(db_options_->info_log,
"Error in committing version edit to MANIFEST: %s",
version_edits.c_str());
for (auto v : versions) {
delete v;
}
// If manifest append failed for whatever reason, the file could be
// corrupted. So we need to force the next version update to start a
// new manifest file.

1
src.mk
View File

@ -419,6 +419,7 @@ MAIN_SOURCES = \
util/bloom_test.cc \
util/coding_test.cc \
util/crc32c_test.cc \
util/defer_test.cc \
util/dynamic_bloom_test.cc \
util/filelock_test.cc \
util/log_write_bench.cc \

52
util/defer.h Normal file
View File

@ -0,0 +1,52 @@
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#pragma once
#include <functional>
namespace rocksdb {
// Defers the execution of the provided function until the Defer
// object goes out of scope.
//
// Usage example:
//
// Status DeferTest() {
// Status s;
// Defer defer([&s]() {
// if (!s.ok()) {
// // do cleanups ...
// }
// });
// // do something ...
// if (!s.ok()) return;
// // do some other things ...
// return s;
// }
//
// The above code ensures that cleanups will always happen on returning.
//
// Without the help of Defer, you can
// 1. every time when !s.ok(), do the cleanup;
// 2. instead of returning when !s.ok(), continue the work only when s.ok(),
// but sometimes, this might lead to nested blocks of "if (s.ok()) {...}".
//
// With the help of Defer, you can centralize the cleanup logic inside the
// lambda passed to Defer, and you can return immediately on failure when necessary.
class Defer final {
public:
Defer(std::function<void()>&& fn) : fn_(std::move(fn)) {}
~Defer() { fn_(); }
// Disallow copy.
Defer(const Defer&) = delete;
Defer& operator=(const Defer&) = delete;
private:
std::function<void()> fn_;
};
} // namespace rocksdb

39
util/defer_test.cc Normal file
View File

@ -0,0 +1,39 @@
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "port/port.h"
#include "port/stack_trace.h"
#include "test_util/testharness.h"
#include "util/defer.h"
namespace rocksdb {
class DeferTest {};
TEST(DeferTest, BlockScope) {
int v = 1;
{
Defer defer([&v]() { v *= 2; });
}
ASSERT_EQ(2, v);
}
TEST(DeferTest, FunctionScope) {
int v = 1;
auto f = [&v]() {
Defer defer([&v]() { v *= 2; });
v = 2;
};
f();
ASSERT_EQ(4, v);
}
} // namespace rocksdb
int main(int argc, char** argv) {
rocksdb::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}