Commit 2fcb96be authored by Chip Turner's avatar Chip Turner

Try again: folly::Singleton, a class for managing singletons

Summary:
Singletons are surprisingly tricky in a codebase where
libraries depend on one another.  folly::Singleton hopes to make this
process more reliable by ensuring object creation happens in a safe
order, that destruction is possible, and that singletons are created
on-demand.

The basic fbcode use intention is to invoke registration completion in
initFacebook, so users need only declare singletons via
Singleton<ClassName> in their .cpp files.

This diff ties the Singletons into the core Init process, but not hhvm
(which will be a separate diff).

Test Plan: runtests

Reviewed By: joelm@fb.com, hans@fb.com

Subscribers: fbcode-common-diffs@, hphp-diffs@, soren, anca, lins, aalexandre, ps, trunkagent, lucian, hannesr, yfeldblum, maxwellsayles

FB internal diff: D1453135
parent 1e5e33a5
......@@ -109,6 +109,20 @@ void checkFopenErrorExplicit(FILE* fp, int savedErrno, Args&&... args) {
}
}
template <typename E, typename V, typename... Args>
void throwOnFail(V&& value, Args&&... args) {
if (!value) {
throw E(std::forward<Args>(args)...);
}
}
/**
* If cond is not true, raise an exception of type E. E must have a ctor that
* works with const char* (a description of the failure).
*/
#define CHECK_THROW(cond, E) \
::folly::throwOnFail<E>((cond), "Check failed: " #cond)
} // namespace folly
#endif /* FOLLY_EXCEPTION_H_ */
......
/*
* Copyright 2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/Singleton.h>
#include <string>
namespace folly {
SingletonVault::~SingletonVault() { destroyInstances(); }
void SingletonVault::destroyInstances() {
std::lock_guard<std::mutex> guard(mutex_);
CHECK_GE(singletons_.size(), creation_order_.size());
for (auto type_iter = creation_order_.rbegin();
type_iter != creation_order_.rend();
++type_iter) {
auto type = *type_iter;
auto it = singletons_.find(type);
CHECK(it != singletons_.end());
auto& entry = it->second;
std::lock_guard<std::mutex> entry_guard(entry->mutex_);
if (entry->instance.use_count() > 1) {
LOG(ERROR) << "Singleton of type " << type.name() << " has a living "
<< "reference at destroyInstances time; beware! Raw pointer "
<< "is " << entry->instance.get() << " with use_count of "
<< entry->instance.use_count();
}
entry->instance.reset();
entry->state = SingletonEntryState::Dead;
}
creation_order_.clear();
}
SingletonVault* SingletonVault::singleton() {
static SingletonVault vault;
return &vault;
}
}
/*
* Copyright 2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// SingletonVault - a library to manage the creation and destruction
// of interdependent singletons.
//
// Basic usage of this class is very simple; suppose you have a class
// called MyExpensiveService, and you only want to construct one (ie,
// it's a singleton), but you only want to construct it if it is used.
//
// In your .h file:
// class MyExpensiveService { ... };
//
// In your .cpp file:
// namespace { folly::Singleton<MyExpensiveService> the_singleton; }
//
// Code can access it via:
//
// MyExpensiveService* instance = Singleton<MyExpensiveService>::get();
// or
// std::weak_ptr<MyExpensiveService> instance =
// Singleton<MyExpensiveService>::get_weak();
//
// The singleton will be created on demand. If the constructor for
// MyExpensiveService actually makes use of *another* Singleton, then
// the right thing will happen -- that other singleton will complete
// construction before get() returns. However, in the event of a
// circular dependency, a runtime error will occur.
//
// By default, the singleton instance is constructed via new and
// deleted via delete, but this is configurable:
//
// namespace { folly::Singleton<MyExpensiveService> the_singleton(create,
// destroy); }
//
// Where create and destroy are functions, Singleton<T>::CreateFunc
// Singleton<T>::TeardownFunc.
//
// What if you need to destroy all of your singletons? Say, some of
// your singletons manage threads, but you need to fork? Or your unit
// test wants to clean up all global state? Then you can call
// SingletonVault::singleton()->destroyInstances(), which invokes the
// TeardownFunc for each singleton, in the reverse order they were
// created. It is your responsibility to ensure your singletons can
// handle cases where the singletons they depend on go away, however.
#pragma once
#include <folly/Exception.h>
#include <vector>
#include <mutex>
#include <string>
#include <unordered_map>
#include <functional>
#include <typeinfo>
#include <typeindex>
#include <glog/logging.h>
namespace folly {
// For actual usage, please see the Singleton<T> class at the bottom
// of this file; that is what you will actually interact with.
// SingletonVault is the class that manages singleton instances. It
// is unaware of the underlying types of singletons, and simply
// manages lifecycles and invokes CreateFunc and TeardownFunc when
// appropriate. In general, you won't need to interact with the
// SingletonVault itself.
//
// A vault goes through a few stages of life:
//
// 1. Registration phase; singletons can be registered, but no
// singleton can be created.
// 2. registrationComplete() has been called; singletons can no
// longer be registered, but they can be created.
// 3. A vault can return to stage 1 when destroyInstances is called.
//
// In general, you don't need to worry about any of the above; just
// ensure registrationComplete() is called near the top of your main()
// function, otherwise no singletons can be instantiated.
class SingletonVault {
public:
SingletonVault() {};
~SingletonVault();
typedef std::function<void(void*)> TeardownFunc;
typedef std::function<void*(void)> CreateFunc;
// Register a singleton of a given type with the create and teardown
// functions.
void registerSingleton(const std::type_info& type,
CreateFunc create,
TeardownFunc teardown) {
std::lock_guard<std::mutex> guard(mutex_);
CHECK_THROW(state_ == SingletonVaultState::Registering, std::logic_error);
CHECK_THROW(singletons_.find(type) == singletons_.end(), std::logic_error);
auto& entry = singletons_[type];
if (!entry) {
entry.reset(new SingletonEntry);
}
std::lock_guard<std::mutex> entry_guard(entry->mutex_);
CHECK(entry->instance == nullptr);
CHECK(create);
CHECK(teardown);
entry->create = create;
entry->teardown = teardown;
entry->state = SingletonEntryState::Dead;
}
// Mark registration is complete; no more singletons can be
// registered at this point.
void registrationComplete() {
std::lock_guard<std::mutex> guard(mutex_);
CHECK_THROW(state_ == SingletonVaultState::Registering, std::logic_error);
state_ = SingletonVaultState::Running;
}
// Destroy all singletons; when complete, the vault can create
// singletons once again, or remain dormant.
void destroyInstances();
// Retrieve a singleton from the vault, creating it if necessary.
std::shared_ptr<void> get_shared(const std::type_info& type) {
std::unique_lock<std::mutex> lock(mutex_);
CHECK_THROW(state_ == SingletonVaultState::Running, std::logic_error);
auto it = singletons_.find(type);
if (it == singletons_.end()) {
throw std::out_of_range(std::string("non-existent singleton: ") +
type.name());
}
auto& entry = it->second;
std::unique_lock<std::mutex> entry_lock(entry->mutex_);
if (entry->state == SingletonEntryState::BeingBorn) {
throw std::out_of_range(std::string("circular singleton dependency: ") +
type.name());
}
if (entry->instance == nullptr) {
CHECK(entry->state == SingletonEntryState::Dead);
entry->state = SingletonEntryState::BeingBorn;
entry_lock.unlock();
lock.unlock();
// Can't use make_shared -- no support for a custom deleter, sadly.
auto instance = std::shared_ptr<void>(entry->create(), entry->teardown);
lock.lock();
entry_lock.lock();
CHECK(entry->state == SingletonEntryState::BeingBorn);
entry->instance = instance;
entry->state = SingletonEntryState::Living;
creation_order_.push_back(type);
}
CHECK(entry->state == SingletonEntryState::Living);
return entry->instance;
}
// For testing; how many registered and living singletons we have.
size_t registeredSingletonCount() const {
std::lock_guard<std::mutex> guard(mutex_);
return singletons_.size();
}
size_t livingSingletonCount() const {
std::lock_guard<std::mutex> guard(mutex_);
size_t ret = 0;
for (const auto& p : singletons_) {
if (p.second->instance) {
++ret;
}
}
return ret;
}
// A well-known vault; you can actually have others, but this is the
// default.
static SingletonVault* singleton();
private:
// The two stages of life for a vault, as mentioned in the class comment.
enum class SingletonVaultState {
Registering,
Running,
};
// Each singleton in the vault can be in three states: dead
// (registered but never created), being born (running the
// CreateFunc), and living (CreateFunc returned an instance).
enum class SingletonEntryState {
Dead,
BeingBorn,
Living,
};
// An actual instance of a singleton, tracking the instance itself,
// its state as described above, and the create and teardown
// functions.
struct SingletonEntry {
std::mutex mutex_;
std::shared_ptr<void> instance;
CreateFunc create = nullptr;
TeardownFunc teardown = nullptr;
SingletonEntryState state = SingletonEntryState::Dead;
SingletonEntry() = default;
SingletonEntry(const SingletonEntry&) = delete;
SingletonEntry& operator=(const SingletonEntry&) = delete;
SingletonEntry& operator=(SingletonEntry&&) = delete;
SingletonEntry(SingletonEntry&&) = delete;
};
mutable std::mutex mutex_;
typedef std::unique_ptr<SingletonEntry> SingletonEntryPtr;
std::unordered_map<std::type_index, SingletonEntryPtr> singletons_;
std::vector<std::type_index> creation_order_;
SingletonVaultState state_ = SingletonVaultState::Registering;
};
// This is the wrapper class that most users actually interact with.
// It allows for simple access to registering and instantiating
// singletons. Create instances of this class in the global scope of
// type Singleton<T> to register your singleton for later access via
// Singleton<T>::get().
template <typename T>
class Singleton {
public:
typedef std::function<T*(void)> CreateFunc;
typedef std::function<void(T*)> TeardownFunc;
// Generally your program life cycle should be fine with calling
// get() repeatedly rather than saving the reference, and then not
// call get() during process shutdown.
static T* get(SingletonVault* vault = nullptr /* for testing */) {
return get_shared(vault).get();
}
// If, however, you do need to hold a reference to the specific
// singleton, you can try to do so with a weak_ptr. Avoid this when
// possible but the inability to lock the weak pointer can be a
// signal that the vault has been destroyed.
static std::weak_ptr<T> get_weak(SingletonVault* vault =
nullptr /* for testing */) {
return std::weak_ptr<T>(get_shared(vault));
}
Singleton(Singleton::CreateFunc c = nullptr,
Singleton::TeardownFunc t = nullptr,
SingletonVault* vault = nullptr /* for testing */) {
if (c == nullptr) {
c = []() { return new T; };
}
SingletonVault::TeardownFunc teardown;
if (t == nullptr) {
teardown = [](void* v) { delete static_cast<T*>(v); };
} else {
teardown = [t](void* v) { t(static_cast<T*>(v)); };
}
if (vault == nullptr) {
vault = SingletonVault::singleton();
}
vault->registerSingleton(typeid(T), c, teardown);
}
private:
// Don't use this function, it's private for a reason! Using it
// would defeat the *entire purpose* of the vault in that we lose
// the ability to guarantee that, after a destroyInstances is
// called, all instances are, in fact, destroyed. You should use
// weak_ptr if you need to hold a reference to the singleton and
// guarantee briefly that it exists.
//
// Yes, you can just get the weak pointer and lock it, but hopefully
// if you have taken the time to read this far, you see why that
// would be bad.
static std::shared_ptr<T> get_shared(SingletonVault* vault =
nullptr /* for testing */) {
return std::static_pointer_cast<T>(
(vault ?: SingletonVault::singleton())->get_shared(typeid(T)));
}
};
}
/*
* Copyright 2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/Singleton.h>
#include <folly/Benchmark.h>
#include <glog/logging.h>
#include <gtest/gtest.h>
using namespace folly;
// A simple class that tracks how often instances of the class and
// subclasses are created, and the ordering.
struct Watchdog {
static std::vector<Watchdog*> creation_order;
Watchdog() { creation_order.push_back(this); }
~Watchdog() {
if (creation_order.back() != this) {
throw std::out_of_range("Watchdog destruction order mismatch");
}
creation_order.pop_back();
}
Watchdog(const Watchdog&) = delete;
Watchdog& operator=(const Watchdog&) = delete;
Watchdog(Watchdog&&) noexcept = default;
};
std::vector<Watchdog*> Watchdog::creation_order;
// Some basic types we use for tracking.
struct ChildWatchdog : public Watchdog {};
struct GlobalWatchdog : public Watchdog {};
struct UnregisteredWatchdog : public Watchdog {};
namespace {
Singleton<GlobalWatchdog> global_watchdog;
}
// Test basic global usage (the default way singletons will generally
// be used).
TEST(Singleton, BasicGlobalUsage) {
EXPECT_EQ(Watchdog::creation_order.size(), 0);
EXPECT_EQ(SingletonVault::singleton()->registeredSingletonCount(), 1);
EXPECT_EQ(SingletonVault::singleton()->livingSingletonCount(), 0);
auto wd1 = Singleton<GlobalWatchdog>::get();
EXPECT_NE(wd1, nullptr);
EXPECT_EQ(Watchdog::creation_order.size(), 1);
auto wd2 = Singleton<GlobalWatchdog>::get();
EXPECT_NE(wd2, nullptr);
EXPECT_EQ(wd1, wd2);
EXPECT_EQ(Watchdog::creation_order.size(), 1);
SingletonVault::singleton()->destroyInstances();
EXPECT_EQ(Watchdog::creation_order.size(), 0);
}
TEST(Singleton, MissingSingleton) {
EXPECT_THROW([]() { auto u = Singleton<UnregisteredWatchdog>::get(); }(),
std::out_of_range);
}
// Exercise some basic codepaths ensuring registration order and
// destruction order happen as expected, that instances are created
// when expected, etc etc.
TEST(Singleton, BasicUsage) {
SingletonVault vault;
EXPECT_EQ(vault.registeredSingletonCount(), 0);
Singleton<Watchdog> watchdog_singleton(nullptr, nullptr, &vault);
EXPECT_EQ(vault.registeredSingletonCount(), 1);
Singleton<ChildWatchdog> child_watchdog_singleton(nullptr, nullptr, &vault);
EXPECT_EQ(vault.registeredSingletonCount(), 2);
vault.registrationComplete();
Watchdog* s1 = Singleton<Watchdog>::get(&vault);
EXPECT_NE(s1, nullptr);
Watchdog* s2 = Singleton<Watchdog>::get(&vault);
EXPECT_NE(s2, nullptr);
EXPECT_EQ(s1, s2);
auto s3 = Singleton<ChildWatchdog>::get(&vault);
EXPECT_NE(s3, nullptr);
EXPECT_NE(s2, s3);
EXPECT_EQ(vault.registeredSingletonCount(), 2);
EXPECT_EQ(vault.livingSingletonCount(), 2);
vault.destroyInstances();
EXPECT_EQ(vault.registeredSingletonCount(), 2);
EXPECT_EQ(vault.livingSingletonCount(), 0);
}
// Some pathological cases such as getting unregistered singletons,
// double registration, etc.
TEST(Singleton, NaughtyUsage) {
SingletonVault vault;
vault.registrationComplete();
// Unregistered.
EXPECT_THROW(Singleton<Watchdog>::get(), std::out_of_range);
EXPECT_THROW(Singleton<Watchdog>::get(&vault), std::out_of_range);
// Registring singletons after registrationComplete called.
EXPECT_THROW([&vault]() {
Singleton<Watchdog> watchdog_singleton(
nullptr, nullptr, &vault);
}(),
std::logic_error);
EXPECT_THROW([]() { Singleton<Watchdog> watchdog_singleton; }(),
std::logic_error);
SingletonVault vault_2;
EXPECT_THROW(Singleton<Watchdog>::get(&vault_2), std::logic_error);
Singleton<Watchdog> watchdog_singleton(nullptr, nullptr, &vault_2);
// double registration
EXPECT_THROW([&vault_2]() {
Singleton<Watchdog> watchdog_singleton(
nullptr, nullptr, &vault_2);
}(),
std::logic_error);
vault_2.destroyInstances();
// double registration after destroy
EXPECT_THROW([&vault_2]() {
Singleton<Watchdog> watchdog_singleton(
nullptr, nullptr, &vault_2);
}(),
std::logic_error);
}
TEST(Singleton, SharedPtrUsage) {
SingletonVault vault;
EXPECT_EQ(vault.registeredSingletonCount(), 0);
Singleton<Watchdog> watchdog_singleton(nullptr, nullptr, &vault);
EXPECT_EQ(vault.registeredSingletonCount(), 1);
Singleton<ChildWatchdog> child_watchdog_singleton(nullptr, nullptr, &vault);
EXPECT_EQ(vault.registeredSingletonCount(), 2);
vault.registrationComplete();
Watchdog* s1 = Singleton<Watchdog>::get(&vault);
EXPECT_NE(s1, nullptr);
Watchdog* s2 = Singleton<Watchdog>::get(&vault);
EXPECT_NE(s2, nullptr);
EXPECT_EQ(s1, s2);
auto weak_s1 = Singleton<Watchdog>::get_weak(&vault);
auto shared_s1 = weak_s1.lock();
EXPECT_EQ(shared_s1.get(), s1);
EXPECT_EQ(shared_s1.use_count(), 2);
LOG(ERROR) << "The following log message regarding ref counts is expected";
vault.destroyInstances();
EXPECT_EQ(vault.registeredSingletonCount(), 2);
EXPECT_EQ(vault.livingSingletonCount(), 0);
EXPECT_EQ(shared_s1.use_count(), 1);
EXPECT_EQ(shared_s1.get(), s1);
auto locked_s1 = weak_s1.lock();
EXPECT_EQ(locked_s1.get(), s1);
EXPECT_EQ(shared_s1.use_count(), 2);
locked_s1.reset();
EXPECT_EQ(shared_s1.use_count(), 1);
shared_s1.reset();
locked_s1 = weak_s1.lock();
EXPECT_TRUE(weak_s1.expired());
Watchdog* new_s1 = Singleton<Watchdog>::get(&vault);
EXPECT_NE(new_s1, s1);
}
// Some classes to test singleton dependencies. NeedySingleton has a
// dependency on NeededSingleton, which happens during its
// construction.
SingletonVault needy_vault;
struct NeededSingleton {};
struct NeedySingleton {
NeedySingleton() {
auto unused = Singleton<NeededSingleton>::get(&needy_vault);
EXPECT_NE(unused, nullptr);
}
};
// Ensure circular dependencies fail -- a singleton that needs itself, whoops.
SingletonVault self_needy_vault;
struct SelfNeedySingleton {
SelfNeedySingleton() {
auto unused = Singleton<SelfNeedySingleton>::get(&self_needy_vault);
EXPECT_NE(unused, nullptr);
}
};
TEST(Singleton, SingletonDependencies) {
Singleton<NeededSingleton> needed_singleton(nullptr, nullptr, &needy_vault);
Singleton<NeedySingleton> needy_singleton(nullptr, nullptr, &needy_vault);
needy_vault.registrationComplete();
EXPECT_EQ(needy_vault.registeredSingletonCount(), 2);
EXPECT_EQ(needy_vault.livingSingletonCount(), 0);
auto needy = Singleton<NeedySingleton>::get(&needy_vault);
EXPECT_EQ(needy_vault.livingSingletonCount(), 2);
Singleton<SelfNeedySingleton> self_needy_singleton(
nullptr, nullptr, &self_needy_vault);
self_needy_vault.registrationComplete();
EXPECT_THROW([]() {
Singleton<SelfNeedySingleton>::get(&self_needy_vault);
}(),
std::out_of_range);
}
// Benchmarking a normal singleton vs a Meyers singleton vs a Folly
// singleton. Meyers are insanely fast, but (hopefully) Folly
// singletons are fast "enough."
int* getMeyersSingleton() {
static auto ret = new int(0);
return ret;
}
int normal_singleton_value = 0;
int* getNormalSingleton() {
doNotOptimizeAway(&normal_singleton_value);
return &normal_singleton_value;
}
struct BenchmarkSingleton {
int val = 0;
};
BENCHMARK(NormalSingleton, n) {
for (int i = 0; i < n; ++i) {
doNotOptimizeAway(getNormalSingleton());
}
}
BENCHMARK_RELATIVE(MeyersSingleton, n) {
for (int i = 0; i < n; ++i) {
doNotOptimizeAway(getMeyersSingleton());
}
}
BENCHMARK_RELATIVE(FollySingleton, n) {
SingletonVault benchmark_vault;
Singleton<BenchmarkSingleton> benchmark_singleton(
nullptr, nullptr, &benchmark_vault);
benchmark_vault.registrationComplete();
for (int i = 0; i < n; ++i) {
doNotOptimizeAway(Singleton<BenchmarkSingleton>::get(&benchmark_vault));
}
}
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
google::InitGoogleLogging(argv[0]);
google::ParseCommandLineFlags(&argc, &argv, true);
SingletonVault::singleton()->registrationComplete();
auto ret = RUN_ALL_TESTS();
if (!ret) {
folly::runBenchmarksOnFlag();
}
return ret;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment