type_(type__), vault_(vault) {
}
+template <typename T>
+bool SingletonHolder<T>::creationStarted() {
+ // If alive, then creation was of course started.
+ // This is flipped after creating_thread_ was set, and before it was reset.
+ if (state_.load(std::memory_order_acquire) == SingletonHolderState::Living) {
+ return true;
+ }
+
+ // Not yet built. Is it currently in progress?
+ if (creating_thread_.load(std::memory_order_acquire) != std::thread::id()) {
+ return true;
+ }
+
+ return false;
+}
+
template <typename T>
void SingletonHolder<T>::createInstance() {
- // There's no synchronization here, so we may not see the current value
- // for creating_thread if it was set by other thread, but we only care about
- // it if it was set by current thread anyways.
- if (creating_thread_ == std::this_thread::get_id()) {
+ if (creating_thread_.load(std::memory_order_acquire) ==
+ std::this_thread::get_id()) {
LOG(FATAL) << "circular singleton dependency: " << type_.name();
}
std::lock_guard<std::mutex> entry_lock(mutex_);
- if (state_ == SingletonHolderState::Living) {
+ if (state_.load(std::memory_order_acquire) == SingletonHolderState::Living) {
return;
}
- if (state_ == SingletonHolderState::NotRegistered) {
+ if (state_.load(std::memory_order_acquire) ==
+ SingletonHolderState::NotRegistered) {
auto ptr = SingletonVault::stackTraceGetter().load();
LOG(FATAL) << "Creating instance for unregistered singleton: "
<< type_.name() << "\n"
<< "\n" << (ptr ? (*ptr)() : "(not available)");
}
- if (state_ == SingletonHolderState::Living) {
+ if (state_.load(std::memory_order_acquire) == SingletonHolderState::Living) {
return;
}
// Clean up creator thread when complete, and also, in case of errors here,
// so that subsequent attempts don't think this is still in the process of
// being built.
- creating_thread_ = std::thread::id();
+ creating_thread_.store(std::thread::id(), std::memory_order_release);
};
- creating_thread_ = std::this_thread::get_id();
+ creating_thread_.store(std::this_thread::get_id(), std::memory_order_release);
RWSpinLock::ReadHolder rh(&vault_.stateMutex_);
if (vault_.state_ == SingletonVault::SingletonVaultState::Quiescing) {
// This has to be the last step, because once state is Living other threads
// may access instance and instance_weak w/o synchronization.
- state_.store(SingletonHolderState::Living);
+ state_.store(SingletonHolderState::Living, std::memory_order_release);
{
RWSpinLock::WriteHolder wh(&vault_.mutex_);
// Where create and destroy are functions, Singleton<T>::CreateFunc
// Singleton<T>::TeardownFunc.
//
+// The above examples detail a situation where an expensive singleton is loaded
+// on-demand (thus only if needed). However if there is an expensive singleton
+// that will likely be needed, and initialization takes a potentially long time,
+// e.g. while initializing, parsing some files, talking to remote services,
+// making uses of other singletons, and so on, the initialization of those can
+// be scheduled up front, or "eagerly".
+//
+// In that case the singleton can be declared this way:
+//
+// namespace {
+// auto the_singleton =
+// folly::Singleton<MyExpensiveService>(/* optional create, destroy args */)
+// .shouldEagerInit();
+// }
+//
+// This way the singleton's instance is built at program initialization
+// time, or more accurately, when "registrationComplete()" or
+// "startEagerInit()" is called. (More about that below; see the
+// section starting with "A vault goes through a few stages of life".)
+//
// 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
#include <folly/Memory.h>
#include <folly/RWSpinLock.h>
#include <folly/Demangle.h>
+#include <folly/Executor.h>
#include <folly/io/async/Request.h>
#include <algorithm>
#include <condition_variable>
#include <string>
#include <unordered_map>
+#include <unordered_set>
#include <functional>
#include <typeinfo>
#include <typeindex>
virtual TypeDescriptor type() = 0;
virtual bool hasLiveInstance() = 0;
+ virtual void createInstance() = 0;
+ virtual bool creationStarted() = 0;
virtual void destroyInstance() = 0;
protected:
void registerSingleton(CreateFunc c, TeardownFunc t);
void registerSingletonMock(CreateFunc c, TeardownFunc t);
- virtual TypeDescriptor type();
- virtual bool hasLiveInstance();
- virtual void destroyInstance();
+ virtual TypeDescriptor type() override;
+ virtual bool hasLiveInstance() override;
+ virtual void createInstance() override;
+ virtual bool creationStarted() override;
+ virtual void destroyInstance() override;
private:
SingletonHolder(TypeDescriptor type, SingletonVault& vault);
- void createInstance();
-
enum class SingletonHolderState {
NotRegistered,
Dead,
std::atomic<SingletonHolderState> state_{SingletonHolderState::NotRegistered};
// the thread creating the singleton (only valid while creating an object)
- std::thread::id creating_thread_;
+ std::atomic<std::thread::id> creating_thread_;
// The singleton itself and related functions.
singletons_[entry->type()] = entry;
}
+ /**
+ * Called by `Singleton<T>.shouldEagerInit()` to ensure the instance
+ * is built when registrationComplete() is called; see that method
+ * for more info.
+ */
+ void addEagerInitSingleton(detail::SingletonHolderBase* entry) {
+ RWSpinLock::ReadHolder rh(&stateMutex_);
+
+ stateCheck(SingletonVaultState::Running);
+
+ if (UNLIKELY(registrationComplete_)) {
+ throw std::logic_error(
+ "Registering for eager-load after registrationComplete().");
+ }
+
+ RWSpinLock::ReadHolder rhMutex(&mutex_);
+ CHECK_THROW(singletons_.find(entry->type()) != singletons_.end(),
+ std::logic_error);
+
+ RWSpinLock::UpgradedHolder wh(&mutex_);
+ eagerInitSingletons_.insert(entry);
+ }
+
// Mark registration is complete; no more singletons can be
- // registered at this point.
- void registrationComplete() {
+ // registered at this point. Kicks off eagerly-initialized singletons
+ // (if requested; default behavior is to do so).
+ void registrationComplete(bool autoStartEagerInit = true) {
RequestContext::saveContext();
std::atexit([](){ SingletonVault::singleton()->destroyInstances(); });
- RWSpinLock::WriteHolder wh(&stateMutex_);
+ {
+ RWSpinLock::WriteHolder wh(&stateMutex_);
- stateCheck(SingletonVaultState::Running);
+ stateCheck(SingletonVaultState::Running);
- if (type_ == Type::Strict) {
- for (const auto& p: singletons_) {
- if (p.second->hasLiveInstance()) {
- throw std::runtime_error(
- "Singleton created before registration was complete.");
+ if (type_ == Type::Strict) {
+ for (const auto& p: singletons_) {
+ if (p.second->hasLiveInstance()) {
+ throw std::runtime_error(
+ "Singleton created before registration was complete.");
+ }
}
}
+
+ registrationComplete_ = true;
+ }
+
+ if (autoStartEagerInit) {
+ startEagerInit();
+ }
+ }
+
+ /**
+ * If eagerInitExecutor_ is non-nullptr (default is nullptr) then
+ * schedule eager singletons' initializations through it.
+ * Otherwise, initializes them synchronously, in a loop.
+ */
+ void startEagerInit() {
+ std::unordered_set<detail::SingletonHolderBase*> singletonSet;
+ {
+ RWSpinLock::ReadHolder rh(&stateMutex_);
+ stateCheck(SingletonVaultState::Running);
+ if (UNLIKELY(!registrationComplete_)) {
+ throw std::logic_error(
+ "registrationComplete() not yet called");
+ }
+ singletonSet = eagerInitSingletons_; // copy set of pointers
}
- registrationComplete_ = true;
+ auto *exe = eagerInitExecutor_; // default value is nullptr
+ for (auto *single : singletonSet) {
+ if (exe) {
+ eagerInitExecutor_->add([single] {
+ if (!single->creationStarted()) {
+ single->createInstance();
+ }
+ });
+ } else {
+ single->createInstance();
+ }
+ }
+ }
+
+ /**
+ * Provide an executor through which startEagerInit would run tasks.
+ * If there are several singletons which may be independently initialized,
+ * and their construction takes long, they could possibly be run in parallel
+ * to cut down on startup time. Unusual; default (synchronous initialization
+ * in a loop) is probably fine for most use cases, and most apps can most
+ * likely avoid using this.
+ */
+ void setEagerInitExecutor(folly::Executor *exe) {
+ eagerInitExecutor_ = exe;
}
// Destroy all singletons; when complete, the vault can't create
mutable folly::RWSpinLock mutex_;
SingletonMap singletons_;
+ std::unordered_set<detail::SingletonHolderBase*> eagerInitSingletons_;
+ folly::Executor* eagerInitExecutor_{nullptr};
std::vector<detail::TypeDescriptor> creation_order_;
SingletonVaultState state_{SingletonVaultState::Running};
bool registrationComplete_{false};
vault->registerSingleton(&getEntry());
}
+ /**
+ * Should be instantiated as soon as "registrationComplete()" is called.
+ * Singletons are usually lazy-loaded (built on-demand) but for those which
+ * are known to be needed, to avoid the potential lag for objects that take
+ * long to construct during runtime, there is an option to make sure these
+ * are built up-front.
+ *
+ * Use like:
+ * Singleton<Foo> gFooInstance = Singleton<Foo>(...).shouldEagerInit();
+ *
+ * Or alternately, define the singleton as usual, and say
+ * gFooInstance.shouldEagerInit()
+ *
+ * at some point prior to calling registrationComplete().
+ * Then registrationComplete can be called (by default it will kick off
+ * init of the eager singletons); alternately, you can use
+ * startEagerInit().
+ */
+ Singleton& shouldEagerInit() {
+ auto vault = SingletonVault::singleton<VaultTag>();
+ vault->addEagerInitSingleton(&getEntry());
+ return *this;
+ }
+
/**
* Construct and inject a mock singleton which should be used only from tests.
* Unlike regular singletons which are initialized once per process lifetime,
#include <thread>
#include <folly/Singleton.h>
+#include <folly/io/async/EventBase.h>
#include <folly/Benchmark.h>
#include <glog/logging.h>
#include <gtest/gtest.h>
+#include <boost/thread/barrier.hpp>
using namespace folly;
}
}
+namespace {
+struct EagerInitSyncTag {};
+}
+template <typename T, typename Tag = detail::DefaultTag>
+using SingletonEagerInitSync = Singleton<T, Tag, EagerInitSyncTag>;
+TEST(Singleton, SingletonEagerInitSync) {
+ auto& vault = *SingletonVault::singleton<EagerInitSyncTag>();
+ bool didEagerInit = false;
+ auto sing = SingletonEagerInitSync<std::string>(
+ [&] {didEagerInit = true; return new std::string("foo"); })
+ .shouldEagerInit();
+ vault.registrationComplete();
+ EXPECT_TRUE(didEagerInit);
+ sing.get_weak(); // (avoid compile error complaining about unused var 'sing')
+}
+
+namespace {
+struct EagerInitAsyncTag {};
+}
+template <typename T, typename Tag = detail::DefaultTag>
+using SingletonEagerInitAsync = Singleton<T, Tag, EagerInitAsyncTag>;
+TEST(Singleton, SingletonEagerInitAsync) {
+ auto& vault = *SingletonVault::singleton<EagerInitAsyncTag>();
+ bool didEagerInit = false;
+ auto sing = SingletonEagerInitAsync<std::string>(
+ [&] {didEagerInit = true; return new std::string("foo"); })
+ .shouldEagerInit();
+ folly::EventBase eb;
+ vault.setEagerInitExecutor(&eb);
+ vault.registrationComplete();
+ EXPECT_FALSE(didEagerInit);
+ eb.loop();
+ EXPECT_TRUE(didEagerInit);
+ sing.get_weak(); // (avoid compile error complaining about unused var 'sing')
+}
+
+namespace {
+class TestEagerInitParallelExecutor : public folly::Executor {
+ public:
+ explicit TestEagerInitParallelExecutor(const size_t threadCount) {
+ eventBases_.reserve(threadCount);
+ threads_.reserve(threadCount);
+ for (size_t i = 0; i < threadCount; i++) {
+ eventBases_.push_back(std::make_shared<folly::EventBase>());
+ auto eb = eventBases_.back();
+ threads_.emplace_back(std::make_shared<std::thread>(
+ [eb] { eb->loopForever(); }));
+ }
+ }
+
+ virtual ~TestEagerInitParallelExecutor() override {
+ for (auto eb : eventBases_) {
+ eb->runInEventBaseThread([eb] { eb->terminateLoopSoon(); });
+ }
+ for (auto thread : threads_) {
+ thread->join();
+ }
+ }
+
+ virtual void add(folly::Func func) override {
+ const auto index = (counter_ ++) % eventBases_.size();
+ eventBases_[index]->add(func);
+ }
+
+ private:
+ std::vector<std::shared_ptr<folly::EventBase>> eventBases_;
+ std::vector<std::shared_ptr<std::thread>> threads_;
+ std::atomic<size_t> counter_ {0};
+};
+} // namespace
+
+namespace {
+struct EagerInitParallelTag {};
+}
+template <typename T, typename Tag = detail::DefaultTag>
+using SingletonEagerInitParallel = Singleton<T, Tag, EagerInitParallelTag>;
+TEST(Singleton, SingletonEagerInitParallel) {
+ const static size_t kIters = 1000;
+ const static size_t kThreads = 20;
+
+ std::atomic<size_t> initCounter;
+
+ auto& vault = *SingletonVault::singleton<EagerInitParallelTag>();
+
+ auto sing = SingletonEagerInitParallel<std::string>(
+ [&] {++initCounter; return new std::string(""); })
+ .shouldEagerInit();
+
+ for (size_t i = 0; i < kIters; i++) {
+ SCOPE_EXIT {
+ // clean up each time
+ vault.destroyInstances();
+ vault.reenableInstances();
+ };
+
+ initCounter.store(0);
+
+ {
+ boost::barrier barrier(kThreads + 1);
+ TestEagerInitParallelExecutor exe(kThreads);
+ vault.setEagerInitExecutor(&exe);
+ vault.registrationComplete(false);
+
+ EXPECT_EQ(0, initCounter.load());
+
+ for (size_t j = 0; j < kThreads; j++) {
+ exe.add([&] {
+ barrier.wait();
+ vault.startEagerInit();
+ barrier.wait();
+ });
+ }
+
+ barrier.wait(); // to await all threads' readiness
+ barrier.wait(); // to await all threads' completion
+ }
+
+ EXPECT_EQ(1, initCounter.load());
+
+ sing.get_weak(); // (avoid compile error complaining about unused var)
+ }
+}
+
// Benchmarking a normal singleton vs a Meyers singleton vs a Folly
// singleton. Meyers are insanely fast, but (hopefully) Folly
// singletons are fast "enough."