// .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".)
+// This way the singleton's instance is built at program initialization,
+// if the program opted-in to that feature by calling "doEagerInit" or
+// "doEagerInitVia" during its startup.
//
// 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
#include <folly/RWSpinLock.h>
#include <folly/Demangle.h>
#include <folly/Executor.h>
+#include <folly/futures/Future.h>
#include <folly/io/async/Request.h>
#include <algorithm>
/**
* Called by `Singleton<T>.shouldEagerInit()` to ensure the instance
- * is built when registrationComplete() is called; see that method
+ * is built when `doEagerInit[Via]` is called; see those methods
* for more info.
*/
void addEagerInitSingleton(detail::SingletonHolderBase* entry) {
if (UNLIKELY(registrationComplete_)) {
throw std::logic_error(
- "Registering for eager-load after registrationComplete().");
+ "Registering for eager-load after registrationComplete().");
}
RWSpinLock::ReadHolder rhMutex(&mutex_);
}
// Mark registration is complete; no more singletons can be
- // registered at this point. Kicks off eagerly-initialized singletons
- // (if requested; default behavior is to do so).
- void registrationComplete(bool autoStartEagerInit = true) {
+ // registered at this point.
+ void registrationComplete() {
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(
+ 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();
- }
+ registrationComplete_ = true;
}
- /**
- * If eagerInitExecutor_ is non-nullptr (default is nullptr) then
- * schedule eager singletons' initializations through it.
- * Otherwise, initializes them synchronously, in a loop.
- */
- void startEagerInit() {
+ /**
+ * Initialize all singletons which were marked as eager-initialized
+ * (using `shouldEagerInit()`). No return value. Propagates exceptions
+ * from constructors / create functions, as is the usual case when calling
+ * for example `Singleton<Foo>::get_weak()`.
+ */
+ void doEagerInit() {
std::unordered_set<detail::SingletonHolderBase*> singletonSet;
{
RWSpinLock::ReadHolder rh(&stateMutex_);
stateCheck(SingletonVaultState::Running);
if (UNLIKELY(!registrationComplete_)) {
- throw std::logic_error(
- "registrationComplete() not yet called");
+ throw std::logic_error("registrationComplete() not yet called");
}
- singletonSet = eagerInitSingletons_; // copy set of pointers
+ singletonSet = eagerInitSingletons_; // copy set of pointers
}
- auto *exe = eagerInitExecutor_; // default value is nullptr
for (auto *single : singletonSet) {
- if (exe) {
- eagerInitExecutor_->add([single] {
- if (!single->creationStarted()) {
- single->createInstance();
- }
- });
- } else {
- single->createInstance();
- }
+ 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.
+ * Schedule eager singletons' initializations through the given executor.
+ * Return a future which is fulfilled after all the initialization functions
+ * complete.
*/
- void setEagerInitExecutor(folly::Executor *exe) {
- eagerInitExecutor_ = exe;
+ Future<Unit> doEagerInitVia(Executor* exe) {
+ 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
+ }
+
+ std::vector<Future<Unit>> resultFutures;
+ for (auto* single : singletonSet) {
+ resultFutures.emplace_back(via(exe).then([single] {
+ if (!single->creationStarted()) {
+ single->createInstance();
+ }
+ }));
+ }
+
+ return collectAll(resultFutures).via(exe).then();
}
// 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};
}
/**
- * Should be instantiated as soon as "registrationComplete()" is called.
+ * Should be instantiated as soon as "doEagerInit[Via]" 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
* Singleton<Foo> gFooInstance = Singleton<Foo>(...).shouldEagerInit();
*
* Or alternately, define the singleton as usual, and say
- * gFooInstance.shouldEagerInit()
+ * 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().
+ * Then doEagerInit() or doEagerInitVia(Executor*) can be called.
*/
Singleton& shouldEagerInit() {
auto vault = SingletonVault::singleton<VaultTag>();
#include <folly/Singleton.h>
#include <folly/io/async/EventBase.h>
-
+#include <folly/test/SingletonTestStructs.h>
#include <folly/Benchmark.h>
#include <glog/logging.h>
using namespace folly;
-// A simple class that tracks how often instances of the class and
-// subclasses are created, and the ordering. Also tracks a global
-// unique counter for each object.
-std::atomic<size_t> global_counter(19770326);
-struct Watchdog {
- static std::vector<Watchdog*> creation_order;
- Watchdog() : serial_number(++global_counter) {
- creation_order.push_back(this);
- }
-
- ~Watchdog() {
- if (creation_order.back() != this) {
- throw std::out_of_range("Watchdog destruction order mismatch");
- }
- creation_order.pop_back();
- }
-
- const size_t serial_number;
- size_t livingWatchdogCount() const { return creation_order.size(); }
-
- 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);
-
- {
- std::shared_ptr<GlobalWatchdog> wd1 = Singleton<GlobalWatchdog>::try_get();
- EXPECT_NE(wd1, nullptr);
- EXPECT_EQ(Watchdog::creation_order.size(), 1);
- std::shared_ptr<GlobalWatchdog> wd2 = Singleton<GlobalWatchdog>::try_get();
- EXPECT_NE(wd2, nullptr);
- EXPECT_EQ(wd1.get(), wd2.get());
- EXPECT_EQ(Watchdog::creation_order.size(), 1);
- }
-
- SingletonVault::singleton()->destroyInstances();
- EXPECT_EQ(Watchdog::creation_order.size(), 0);
-}
-
TEST(Singleton, MissingSingleton) {
EXPECT_DEATH([]() { auto u = Singleton<UnregisteredWatchdog>::try_get(); }(),
"");
[&] {didEagerInit = true; return new std::string("foo"); })
.shouldEagerInit();
vault.registrationComplete();
+ EXPECT_FALSE(didEagerInit);
+ vault.doEagerInit();
EXPECT_TRUE(didEagerInit);
sing.get_weak(); // (avoid compile error complaining about unused var 'sing')
}
[&] {didEagerInit = true; return new std::string("foo"); })
.shouldEagerInit();
folly::EventBase eb;
- vault.setEagerInitExecutor(&eb);
vault.registrationComplete();
EXPECT_FALSE(didEagerInit);
+ auto result = vault.doEagerInitVia(&eb); // a Future<Unit> is returned
eb.loop();
+ result.get(); // ensure this completed successfully and didn't hang forever
EXPECT_TRUE(didEagerInit);
sing.get_weak(); // (avoid compile error complaining about unused var 'sing')
}
initCounter.store(0);
{
- boost::barrier barrier(kThreads + 1);
+ std::vector<std::shared_ptr<std::thread>> threads;
+ boost::barrier barrier(kThreads);
TestEagerInitParallelExecutor exe(kThreads);
- vault.setEagerInitExecutor(&exe);
- vault.registrationComplete(false);
+ vault.registrationComplete();
EXPECT_EQ(0, initCounter.load());
for (size_t j = 0; j < kThreads; j++) {
- exe.add([&] {
+ threads.push_back(std::make_shared<std::thread>([&] {
barrier.wait();
- vault.startEagerInit();
- barrier.wait();
- });
+ vault.doEagerInitVia(&exe).get();
+ }));
}
- barrier.wait(); // to await all threads' readiness
- barrier.wait(); // to await all threads' completion
+ for (auto thread : threads) {
+ thread->join();
+ }
}
EXPECT_EQ(1, initCounter.load());
--- /dev/null
+/*
+ * Copyright 2015 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 <memory>
+#include <vector>
+
+#include <folly/Singleton.h>
+#include <folly/Benchmark.h>
+#include <folly/test/SingletonTestStructs.h>
+
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+/*
+ * This test needs to be in its own file, as a standalone program.
+ * We want to ensure no other singletons are registered, so we can
+ * rely on some expectations about registered and living counts, etc.
+ * All other tests should go in `SingletonTest.cpp`.
+ */
+
+using namespace folly;
+
+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);
+
+ {
+ std::shared_ptr<GlobalWatchdog> wd1 = Singleton<GlobalWatchdog>::try_get();
+ EXPECT_NE(wd1, nullptr);
+ EXPECT_EQ(Watchdog::creation_order().size(), 1);
+ std::shared_ptr<GlobalWatchdog> wd2 = Singleton<GlobalWatchdog>::try_get();
+ EXPECT_NE(wd2, nullptr);
+ EXPECT_EQ(wd1.get(), wd2.get());
+ EXPECT_EQ(Watchdog::creation_order().size(), 1);
+ }
+
+ SingletonVault::singleton()->destroyInstances();
+ EXPECT_EQ(Watchdog::creation_order().size(), 0);
+}
--- /dev/null
+/*
+ * Copyright 2015 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.
+ */
+#pragma once
+
+/*
+ * Copyright 2015 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 <memory>
+#include <vector>
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+// A simple class that tracks how often instances of the class and
+// subclasses are created, and the ordering. Also tracks a global
+// unique counter for each object.
+std::atomic<size_t> global_counter(19770326);
+struct Watchdog {
+ static std::vector<Watchdog*>& creation_order() {
+ static std::vector<Watchdog*> ret;
+ return ret;
+ }
+
+ Watchdog() : serial_number(++global_counter) {
+ creation_order().push_back(this);
+ }
+
+ ~Watchdog() {
+ if (creation_order().back() != this) {
+ throw std::out_of_range("Watchdog destruction order mismatch");
+ }
+ creation_order().pop_back();
+ }
+
+ const size_t serial_number;
+ size_t livingWatchdogCount() const { return creation_order().size(); }
+
+ Watchdog(const Watchdog&) = delete;
+ Watchdog& operator=(const Watchdog&) = delete;
+ Watchdog(Watchdog&&) noexcept = default;
+ Watchdog& operator=(Watchdog&&) noexcept = default;
+};
+
+// Some basic types we use for tracking.
+struct ChildWatchdog : public Watchdog {};
+struct GlobalWatchdog : public Watchdog {};
+struct UnregisteredWatchdog : public Watchdog {};