* limitations under the License.
*/
+#include <thread>
+
#include <folly/experimental/Singleton.h>
#include <folly/Benchmark.h>
}
const size_t serial_number;
+ size_t livingWatchdogCount() const { return creation_order.size(); }
Watchdog(const Watchdog&) = delete;
Watchdog& operator=(const Watchdog&) = delete;
EXPECT_EQ(vault.livingSingletonCount(), 0);
}
+TEST(Singleton, DirectUsage) {
+ SingletonVault vault;
+
+ EXPECT_EQ(vault.registeredSingletonCount(), 0);
+
+ // Verify we can get to the underlying singletons via directly using
+ // the singleton definition.
+ Singleton<Watchdog> watchdog(nullptr, nullptr, &vault);
+ Singleton<Watchdog> named_watchdog("named", nullptr, nullptr, &vault);
+ EXPECT_EQ(vault.registeredSingletonCount(), 2);
+ vault.registrationComplete();
+
+ EXPECT_NE(watchdog.ptr(), nullptr);
+ EXPECT_EQ(watchdog.ptr(), Singleton<Watchdog>::get(&vault));
+ EXPECT_NE(watchdog.ptr(), named_watchdog.ptr());
+ EXPECT_EQ(watchdog->livingWatchdogCount(), 2);
+ EXPECT_EQ((*watchdog).livingWatchdogCount(), 2);
+}
+
+TEST(Singleton, NamedUsage) {
+ SingletonVault vault;
+
+ EXPECT_EQ(vault.registeredSingletonCount(), 0);
+
+ // Define two named Watchdog singletons and one unnamed singleton.
+ Singleton<Watchdog> watchdog1_singleton(
+ "watchdog1", nullptr, nullptr, &vault);
+ EXPECT_EQ(vault.registeredSingletonCount(), 1);
+ Singleton<Watchdog> watchdog2_singleton(
+ "watchdog2", nullptr, nullptr, &vault);
+ EXPECT_EQ(vault.registeredSingletonCount(), 2);
+ Singleton<Watchdog> watchdog3_singleton(nullptr, nullptr, &vault);
+ EXPECT_EQ(vault.registeredSingletonCount(), 3);
+
+ vault.registrationComplete();
+
+ // Verify our three singletons are distinct and non-nullptr.
+ Watchdog* s1 = Singleton<Watchdog>::get("watchdog1", &vault);
+ EXPECT_EQ(s1, watchdog1_singleton.ptr());
+ Watchdog* s2 = Singleton<Watchdog>::get("watchdog2", &vault);
+ EXPECT_EQ(s2, watchdog2_singleton.ptr());
+ EXPECT_NE(s1, s2);
+ Watchdog* s3 = Singleton<Watchdog>::get(&vault);
+ EXPECT_EQ(s3, watchdog3_singleton.ptr());
+ EXPECT_NE(s3, s1);
+ EXPECT_NE(s3, s2);
+
+ // Verify the "default" singleton is the same as the empty string
+ // singleton.
+ Watchdog* s4 = Singleton<Watchdog>::get("", &vault);
+ EXPECT_EQ(s4, watchdog3_singleton.ptr());
+}
+
// Some pathological cases such as getting unregistered singletons,
// double registration, etc.
TEST(Singleton, NaughtyUsage) {
- SingletonVault vault;
+ SingletonVault vault(SingletonVault::Type::Strict);
vault.registrationComplete();
// Unregistered.
}(),
std::logic_error);
- EXPECT_THROW([]() { Singleton<Watchdog> watchdog_singleton; }(),
- std::logic_error);
-
- SingletonVault vault_2;
+ SingletonVault vault_2(SingletonVault::Type::Strict);
EXPECT_THROW(Singleton<Watchdog>::get(&vault_2), std::logic_error);
Singleton<Watchdog> watchdog_singleton(nullptr, nullptr, &vault_2);
// double registration
Singleton<ChildWatchdog> child_watchdog_singleton(nullptr, nullptr, &vault);
EXPECT_EQ(vault.registeredSingletonCount(), 2);
+ Singleton<Watchdog> named_watchdog_singleton(
+ "a_name", nullptr, nullptr, &vault);
vault.registrationComplete();
Watchdog* s1 = Singleton<Watchdog>::get(&vault);
EXPECT_EQ(shared_s1.get(), s1);
EXPECT_EQ(shared_s1.use_count(), 2);
+ {
+ auto named_weak_s1 = Singleton<Watchdog>::get_weak("a_name", &vault);
+ auto locked = named_weak_s1.lock();
+ EXPECT_NE(locked.get(), shared_s1.get());
+ }
+
LOG(ERROR) << "The following log message regarding ref counts is expected";
vault.destroyInstances();
- EXPECT_EQ(vault.registeredSingletonCount(), 2);
+ EXPECT_EQ(vault.registeredSingletonCount(), 3);
EXPECT_EQ(vault.livingSingletonCount(), 0);
EXPECT_EQ(shared_s1.use_count(), 1);
locked_s1 = weak_s1.lock();
EXPECT_TRUE(weak_s1.expired());
+ auto empty_s1 = Singleton<Watchdog>::get_weak(&vault);
+ EXPECT_FALSE(empty_s1.lock());
+
+ vault.reenableInstances();
+
+ // Singleton should be re-created only after reenableInstances() was called.
Watchdog* new_s1 = Singleton<Watchdog>::get(&vault);
EXPECT_NE(new_s1->serial_number, old_serial);
}
std::out_of_range);
}
+// A test to ensure multiple threads contending on singleton creation
+// properly wait for creation rather than thinking it is a circular
+// dependency.
+class Slowpoke : public Watchdog {
+ public:
+ Slowpoke() { std::this_thread::sleep_for(std::chrono::milliseconds(10)); }
+};
+
+TEST(Singleton, SingletonConcurrency) {
+ SingletonVault vault;
+ Singleton<Slowpoke> slowpoke_singleton(nullptr, nullptr, &vault);
+ vault.registrationComplete();
+
+ std::mutex gatekeeper;
+ gatekeeper.lock();
+ auto func = [&vault, &gatekeeper]() {
+ gatekeeper.lock();
+ gatekeeper.unlock();
+ auto unused = Singleton<Slowpoke>::get(&vault);
+ };
+
+ EXPECT_EQ(vault.livingSingletonCount(), 0);
+ std::vector<std::thread> threads;
+ for (int i = 0; i < 100; ++i) {
+ threads.emplace_back(func);
+ }
+ // If circular dependency checks fail, the unlock would trigger a
+ // crash. Instead, it succeeds, and we have exactly one living
+ // singleton.
+ gatekeeper.unlock();
+ for (auto& t : threads) {
+ t.join();
+ }
+ EXPECT_EQ(vault.livingSingletonCount(), 1);
+}
+
+TEST(Singleton, SingletonConcurrencyStress) {
+ SingletonVault vault;
+ Singleton<Slowpoke> slowpoke_singleton(nullptr, nullptr, &vault);
+
+ std::vector<std::thread> ts;
+ for (size_t i = 0; i < 100; ++i) {
+ ts.emplace_back([&]() {
+ slowpoke_singleton.get_weak(&vault).lock();
+ });
+ }
+
+ for (size_t i = 0; i < 100; ++i) {
+ std::chrono::milliseconds d(20);
+
+ std::this_thread::sleep_for(d);
+ vault.destroyInstances();
+ std::this_thread::sleep_for(d);
+ vault.destroyInstances();
+ }
+
+ for (auto& t : ts) {
+ t.join();
+ }
+}
+
// Benchmarking a normal singleton vs a Meyers singleton vs a Folly
// singleton. Meyers are insanely fast, but (hopefully) Folly
// singletons are fast "enough."
return &normal_singleton_value;
}
+// Verify that existing Singleton's can be overridden
+// using the make_mock functionality.
+TEST(Singleton, make_mock) {
+ SingletonVault vault(SingletonVault::Type::Strict);
+ Singleton<Watchdog> watchdog_singleton(nullptr, nullptr, &vault);
+ vault.registrationComplete();
+
+ // Registring singletons after registrationComplete called works
+ // with make_mock (but not with Singleton ctor).
+ EXPECT_EQ(vault.registeredSingletonCount(), 1);
+ int serial_count_first = Singleton<Watchdog>::get(&vault)->serial_number;
+
+ // Override existing mock using make_mock.
+ Singleton<Watchdog>::make_mock(nullptr, nullptr, &vault);
+
+ EXPECT_EQ(vault.registeredSingletonCount(), 1);
+ int serial_count_mock = Singleton<Watchdog>::get(&vault)->serial_number;
+
+ // If serial_count value is the same, then singleton was not replaced.
+ EXPECT_NE(serial_count_first, serial_count_mock);
+}
+
struct BenchmarkSingleton {
int val = 0;
};
BENCHMARK(NormalSingleton, n) {
- for (int i = 0; i < n; ++i) {
+ for (size_t i = 0; i < n; ++i) {
doNotOptimizeAway(getNormalSingleton());
}
}
BENCHMARK_RELATIVE(MeyersSingleton, n) {
- for (int i = 0; i < n; ++i) {
+ for (size_t i = 0; i < n; ++i) {
doNotOptimizeAway(getMeyersSingleton());
}
}
-BENCHMARK_RELATIVE(FollySingleton, n) {
+BENCHMARK_RELATIVE(FollySingletonSlow, n) {
SingletonVault benchmark_vault;
Singleton<BenchmarkSingleton> benchmark_singleton(
nullptr, nullptr, &benchmark_vault);
benchmark_vault.registrationComplete();
- for (int i = 0; i < n; ++i) {
+ for (size_t i = 0; i < n; ++i) {
doNotOptimizeAway(Singleton<BenchmarkSingleton>::get(&benchmark_vault));
}
}
+BENCHMARK_RELATIVE(FollySingletonFast, n) {
+ SingletonVault benchmark_vault;
+ Singleton<BenchmarkSingleton> benchmark_singleton(
+ nullptr, nullptr, &benchmark_vault);
+ benchmark_vault.registrationComplete();
+
+ for (size_t i = 0; i < n; ++i) {
+ doNotOptimizeAway(benchmark_singleton.get_fast());
+ }
+}
+
+BENCHMARK_RELATIVE(FollySingletonFastWeak, n) {
+ SingletonVault benchmark_vault;
+ Singleton<BenchmarkSingleton> benchmark_singleton(
+ nullptr, nullptr, &benchmark_vault);
+ benchmark_vault.registrationComplete();
+
+ for (size_t i = 0; i < n; ++i) {
+ benchmark_singleton.get_weak_fast();
+ }
+}
+
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
google::InitGoogleLogging(argv[0]);