From: Andrii Grynenko Date: Wed, 4 Feb 2015 00:48:31 +0000 (-0800) Subject: Using type-tags for test SingletonVaults X-Git-Tag: v0.25.0~14 X-Git-Url: http://plrg.eecs.uci.edu/git/?a=commitdiff_plain;h=75d52926e0368733c79b55def0801dba6e41ea5e;p=folly.git Using type-tags for test SingletonVaults Summary: This makes test SingletonVaults more similar to global SingletonVault. It also ensures that every SingletonVault is a singleton, which allows potential optimization of making each SingletonEntry a Singleton too, which itself will make get/get_weak as efficient as get_fast/get_weak_fast. Test Plan: unit test Reviewed By: chip@fb.com Subscribers: folly-diffs@, yfeldblum FB internal diff: D1823663 Signature: t1:1823663:1423188884:4ab2b47cfba270a66613f6f2458b6affc0ed96ad --- diff --git a/folly/experimental/Singleton.cpp b/folly/experimental/Singleton.cpp index 560175fb..20522b24 100644 --- a/folly/experimental/Singleton.cpp +++ b/folly/experimental/Singleton.cpp @@ -86,11 +86,6 @@ void SingletonVault::reenableInstances() { state_ = SingletonVaultState::Running; } -SingletonVault* SingletonVault::singleton() { - static SingletonVault* vault = new SingletonVault(); - return vault; -} - void SingletonVault::scheduleDestroyInstances() { RequestContext::getStaticContext(); diff --git a/folly/experimental/Singleton.h b/folly/experimental/Singleton.h index a715a278..6cb94857 100644 --- a/folly/experimental/Singleton.h +++ b/folly/experimental/Singleton.h @@ -399,7 +399,17 @@ class SingletonVault { // A well-known vault; you can actually have others, but this is the // default. - static SingletonVault* singleton(); + static SingletonVault* singleton() { + return singleton<>(); + } + + // Gets singleton vault for any Tag. Non-default tag should be used in unit + // tests only. + template + static SingletonVault* singleton() { + static SingletonVault* vault = new SingletonVault(); + return vault; + } private: // The two stages of life for a vault, as mentioned in the class comment. @@ -534,7 +544,9 @@ class SingletonVault { // singletons. Create instances of this class in the global scope of // type Singleton to register your singleton for later access via // Singleton::get(). -template +template class Singleton { public: typedef std::function CreateFunc; @@ -543,9 +555,9 @@ class Singleton { // 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 */) { + static T* get() { return static_cast( - (vault ?: SingletonVault::singleton())->get_ptr(typeDescriptor())); + SingletonVault::singleton()->get_ptr(typeDescriptor())); } // Same as get, but should be preffered to it in the same compilation @@ -554,7 +566,7 @@ class Singleton { if (LIKELY(entry_->state == detail::SingletonEntryState::Living)) { return reinterpret_cast(entry_->instance_ptr); } else { - return get(vault_); + return get(); } } @@ -562,10 +574,9 @@ class Singleton { // 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 get_weak( - SingletonVault* vault = nullptr /* for testing */) { + static std::weak_ptr get_weak() { auto weak_void_ptr = - (vault ?: SingletonVault::singleton())->get_weak(typeDescriptor()); + (SingletonVault::singleton())->get_weak(typeDescriptor()); // This is ugly and inefficient, but there's no other way to do it, because // there's no static_pointer_cast for weak_ptr. @@ -588,7 +599,7 @@ class Singleton { } return std::static_pointer_cast(shared_void_ptr); } else { - return get_weak(vault_); + return get_weak(); } } @@ -599,26 +610,19 @@ class Singleton { T* operator->() { return ptr(); } explicit Singleton(std::nullptr_t _ = nullptr, - Singleton::TeardownFunc t = nullptr, - SingletonVault* vault = nullptr) : - Singleton ([]() { return new T; }, - std::move(t), - vault) { + Singleton::TeardownFunc t = nullptr) : + Singleton ([]() { return new T; }, std::move(t)) { } explicit Singleton(Singleton::CreateFunc c, - Singleton::TeardownFunc t = nullptr, - SingletonVault* vault = nullptr) { + Singleton::TeardownFunc t = nullptr) { if (c == nullptr) { throw std::logic_error( "nullptr_t should be passed if you want T to be default constructed"); } - if (vault == nullptr) { - vault = SingletonVault::singleton(); - } + auto vault = SingletonVault::singleton(); - vault_ = vault; entry_ = &(vault->registerSingleton(typeDescriptor(), c, getTeardownFunc(t))); } @@ -634,22 +638,18 @@ class Singleton { * regular singletons. */ static void make_mock(std::nullptr_t c = nullptr, - typename Singleton::TeardownFunc t = nullptr, - SingletonVault* vault = nullptr /* for testing */ ) { - make_mock([]() { return new T; }, t, vault); + typename Singleton::TeardownFunc t = nullptr) { + make_mock([]() { return new T; }, t); } static void make_mock(CreateFunc c, - typename Singleton::TeardownFunc t = nullptr, - SingletonVault* vault = nullptr /* for testing */ ) { + typename Singleton::TeardownFunc t = nullptr) { if (c == nullptr) { throw std::logic_error( "nullptr_t should be passed if you want T to be default constructed"); } - if (vault == nullptr) { - vault = SingletonVault::singleton(); - } + auto vault = SingletonVault::singleton(); vault->registerMockSingleton( typeDescriptor(), @@ -680,7 +680,6 @@ class Singleton { // We rely on the fact that Singleton destructor won't reset this pointer, so // it can be "safely" used even after static Singleton object is destroyed. detail::SingletonEntry* entry_; - SingletonVault* vault_; }; } diff --git a/folly/experimental/test/SingletonTest.cpp b/folly/experimental/test/SingletonTest.cpp index 25b270a6..57b25f40 100644 --- a/folly/experimental/test/SingletonTest.cpp +++ b/folly/experimental/test/SingletonTest.cpp @@ -83,30 +83,34 @@ TEST(Singleton, MissingSingleton) { std::out_of_range); } +struct BasicUsageTag {}; +template +using SingletonBasicUsage = Singleton ; + // 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; + auto& vault = *SingletonVault::singleton(); EXPECT_EQ(vault.registeredSingletonCount(), 0); - Singleton watchdog_singleton(nullptr, nullptr, &vault); + SingletonBasicUsage watchdog_singleton; EXPECT_EQ(vault.registeredSingletonCount(), 1); - Singleton child_watchdog_singleton(nullptr, nullptr, &vault); + SingletonBasicUsage child_watchdog_singleton; EXPECT_EQ(vault.registeredSingletonCount(), 2); vault.registrationComplete(); - Watchdog* s1 = Singleton::get(&vault); + Watchdog* s1 = SingletonBasicUsage::get(); EXPECT_NE(s1, nullptr); - Watchdog* s2 = Singleton::get(&vault); + Watchdog* s2 = SingletonBasicUsage::get(); EXPECT_NE(s2, nullptr); EXPECT_EQ(s1, s2); - auto s3 = Singleton::get(&vault); + auto s3 = SingletonBasicUsage::get(); EXPECT_NE(s3, nullptr); EXPECT_NE(s2, s3); @@ -118,28 +122,38 @@ TEST(Singleton, BasicUsage) { EXPECT_EQ(vault.livingSingletonCount(), 0); } +struct DirectUsageTag {}; +template +using SingletonDirectUsage = Singleton ; + TEST(Singleton, DirectUsage) { - SingletonVault vault; + auto& vault = *SingletonVault::singleton(); EXPECT_EQ(vault.registeredSingletonCount(), 0); // Verify we can get to the underlying singletons via directly using // the singleton definition. - Singleton watchdog(nullptr, nullptr, &vault); + SingletonDirectUsage watchdog; struct TestTag {}; - Singleton named_watchdog(nullptr, nullptr, &vault); + SingletonDirectUsage named_watchdog; EXPECT_EQ(vault.registeredSingletonCount(), 2); vault.registrationComplete(); EXPECT_NE(watchdog.ptr(), nullptr); - EXPECT_EQ(watchdog.ptr(), Singleton::get(&vault)); + EXPECT_EQ(watchdog.ptr(), SingletonDirectUsage::get()); EXPECT_NE(watchdog.ptr(), named_watchdog.ptr()); EXPECT_EQ(watchdog->livingWatchdogCount(), 2); EXPECT_EQ((*watchdog).livingWatchdogCount(), 2); + + vault.destroyInstances(); } +struct NamedUsageTag {}; +template +using SingletonNamedUsage = Singleton ; + TEST(Singleton, NamedUsage) { - SingletonVault vault; + auto& vault = *SingletonVault::singleton(); EXPECT_EQ(vault.registeredSingletonCount(), 0); @@ -147,96 +161,105 @@ TEST(Singleton, NamedUsage) { struct Watchdog1 {}; struct Watchdog2 {}; typedef detail::DefaultTag Watchdog3; - Singleton watchdog1_singleton(nullptr, nullptr, &vault); + SingletonNamedUsage watchdog1_singleton; EXPECT_EQ(vault.registeredSingletonCount(), 1); - Singleton watchdog2_singleton(nullptr, nullptr, &vault); + SingletonNamedUsage watchdog2_singleton; EXPECT_EQ(vault.registeredSingletonCount(), 2); - Singleton watchdog3_singleton(nullptr, nullptr, &vault); + SingletonNamedUsage watchdog3_singleton; EXPECT_EQ(vault.registeredSingletonCount(), 3); vault.registrationComplete(); // Verify our three singletons are distinct and non-nullptr. - Watchdog* s1 = Singleton::get(&vault); + Watchdog* s1 = SingletonNamedUsage::get(); EXPECT_EQ(s1, watchdog1_singleton.ptr()); - Watchdog* s2 = Singleton::get(&vault); + Watchdog* s2 = SingletonNamedUsage::get(); EXPECT_EQ(s2, watchdog2_singleton.ptr()); EXPECT_NE(s1, s2); - Watchdog* s3 = Singleton::get(&vault); + Watchdog* s3 = SingletonNamedUsage::get(); EXPECT_EQ(s3, watchdog3_singleton.ptr()); EXPECT_NE(s3, s1); EXPECT_NE(s3, s2); // Verify the "default" singleton is the same as the DefaultTag-tagged // singleton. - Watchdog* s4 = Singleton::get(&vault); + Watchdog* s4 = SingletonNamedUsage::get(); EXPECT_EQ(s4, watchdog3_singleton.ptr()); + + vault.destroyInstances(); } +struct NaughtyUsageTag {}; +template +using SingletonNaughtyUsage = Singleton ; +struct NaughtyUsageTag2 {}; +template +using SingletonNaughtyUsage2 = Singleton ; + // Some pathological cases such as getting unregistered singletons, // double registration, etc. TEST(Singleton, NaughtyUsage) { - SingletonVault vault(SingletonVault::Type::Strict); + auto& vault = *SingletonVault::singleton(); + vault.registrationComplete(); // Unregistered. EXPECT_THROW(Singleton::get(), std::out_of_range); - EXPECT_THROW(Singleton::get(&vault), std::out_of_range); - - // Registring singletons after registrationComplete called. - EXPECT_THROW([&vault]() { - Singleton watchdog_singleton( - nullptr, nullptr, &vault); - }(), - std::logic_error); - - SingletonVault vault_2(SingletonVault::Type::Strict); - EXPECT_THROW(Singleton::get(&vault_2), std::logic_error); - Singleton watchdog_singleton(nullptr, nullptr, &vault_2); + EXPECT_THROW(SingletonNaughtyUsage::get(), std::out_of_range); + + vault.destroyInstances(); + + auto& vault2 = *SingletonVault::singleton(); + + EXPECT_THROW(SingletonNaughtyUsage2::get(), std::logic_error); + SingletonNaughtyUsage2 watchdog_singleton; // double registration - EXPECT_THROW([&vault_2]() { - Singleton watchdog_singleton( - nullptr, nullptr, &vault_2); - }(), - std::logic_error); - vault_2.destroyInstances(); + EXPECT_THROW([]() { + SingletonNaughtyUsage2 watchdog_singleton; + }(), + std::logic_error); + vault2.destroyInstances(); // double registration after destroy - EXPECT_THROW([&vault_2]() { - Singleton watchdog_singleton( - nullptr, nullptr, &vault_2); - }(), - std::logic_error); + EXPECT_THROW([]() { + SingletonNaughtyUsage2 watchdog_singleton; + }(), + std::logic_error); } +struct SharedPtrUsageTag {}; +template +using SingletonSharedPtrUsage = Singleton ; + TEST(Singleton, SharedPtrUsage) { - SingletonVault vault; + auto& vault = *SingletonVault::singleton(); EXPECT_EQ(vault.registeredSingletonCount(), 0); - Singleton watchdog_singleton(nullptr, nullptr, &vault); + SingletonSharedPtrUsage watchdog_singleton; EXPECT_EQ(vault.registeredSingletonCount(), 1); - Singleton child_watchdog_singleton(nullptr, nullptr, &vault); + SingletonSharedPtrUsage child_watchdog_singleton; EXPECT_EQ(vault.registeredSingletonCount(), 2); struct ATag {}; - Singleton named_watchdog_singleton(nullptr, nullptr, &vault); + SingletonSharedPtrUsage named_watchdog_singleton; vault.registrationComplete(); - Watchdog* s1 = Singleton::get(&vault); + Watchdog* s1 = SingletonSharedPtrUsage::get(); EXPECT_NE(s1, nullptr); - Watchdog* s2 = Singleton::get(&vault); + Watchdog* s2 = SingletonSharedPtrUsage::get(); EXPECT_NE(s2, nullptr); EXPECT_EQ(s1, s2); - auto weak_s1 = Singleton::get_weak(&vault); + auto weak_s1 = SingletonSharedPtrUsage::get_weak(); auto shared_s1 = weak_s1.lock(); EXPECT_EQ(shared_s1.get(), s1); EXPECT_EQ(shared_s1.use_count(), 2); { - auto named_weak_s1 = Singleton::get_weak(&vault); + auto named_weak_s1 = + SingletonSharedPtrUsage::get_weak(); auto locked = named_weak_s1.lock(); EXPECT_NE(locked.get(), shared_s1.get()); } @@ -268,16 +291,16 @@ TEST(Singleton, SharedPtrUsage) { locked_s1 = weak_s1.lock(); EXPECT_TRUE(weak_s1.expired()); - auto empty_s1 = Singleton::get_weak(&vault); + auto empty_s1 = SingletonSharedPtrUsage::get_weak(); EXPECT_FALSE(empty_s1.lock()); vault.reenableInstances(); // Singleton should be re-created only after reenableInstances() was called. - Watchdog* new_s1 = Singleton::get(&vault); + Watchdog* new_s1 = SingletonSharedPtrUsage::get(); EXPECT_NE(new_s1->serial_number, old_serial); - auto new_s1_weak = Singleton::get_weak(&vault); + auto new_s1_weak = SingletonSharedPtrUsage::get_weak(); auto new_s1_shared = new_s1_weak.lock(); std::thread t([new_s1_shared]() mutable { std::this_thread::sleep_for(std::chrono::seconds{2}); @@ -298,43 +321,51 @@ TEST(Singleton, SharedPtrUsage) { // Some classes to test singleton dependencies. NeedySingleton has a // dependency on NeededSingleton, which happens during its // construction. -SingletonVault needy_vault; +struct NeedyTag {}; +template +using SingletonNeedy = Singleton ; struct NeededSingleton {}; struct NeedySingleton { NeedySingleton() { - auto unused = Singleton::get(&needy_vault); + auto unused = SingletonNeedy::get(); EXPECT_NE(unused, nullptr); } }; // Ensure circular dependencies fail -- a singleton that needs itself, whoops. -SingletonVault self_needy_vault; +struct SelfNeedyTag {}; +template +using SingletonSelfNeedy = Singleton ; + struct SelfNeedySingleton { SelfNeedySingleton() { - auto unused = Singleton::get(&self_needy_vault); + auto unused = SingletonSelfNeedy::get(); EXPECT_NE(unused, nullptr); } }; TEST(Singleton, SingletonDependencies) { - Singleton needed_singleton(nullptr, nullptr, &needy_vault); - Singleton needy_singleton(nullptr, nullptr, &needy_vault); + SingletonNeedy needed_singleton; + SingletonNeedy needy_singleton; + auto& needy_vault = *SingletonVault::singleton(); + needy_vault.registrationComplete(); EXPECT_EQ(needy_vault.registeredSingletonCount(), 2); EXPECT_EQ(needy_vault.livingSingletonCount(), 0); - auto needy = Singleton::get(&needy_vault); + auto needy = SingletonNeedy::get(); EXPECT_EQ(needy_vault.livingSingletonCount(), 2); - Singleton self_needy_singleton( - nullptr, nullptr, &self_needy_vault); + SingletonSelfNeedy self_needy_singleton; + auto& self_needy_vault = *SingletonVault::singleton(); + self_needy_vault.registrationComplete(); EXPECT_THROW([]() { - Singleton::get(&self_needy_vault); - }(), - std::out_of_range); + SingletonSelfNeedy::get(); + }(), + std::out_of_range); } // A test to ensure multiple threads contending on singleton creation @@ -345,17 +376,21 @@ class Slowpoke : public Watchdog { Slowpoke() { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } }; +struct ConcurrencyTag {}; +template +using SingletonConcurrency = Singleton ; + TEST(Singleton, SingletonConcurrency) { - SingletonVault vault; - Singleton slowpoke_singleton(nullptr, nullptr, &vault); + auto& vault = *SingletonVault::singleton(); + SingletonConcurrency slowpoke_singleton; vault.registrationComplete(); std::mutex gatekeeper; gatekeeper.lock(); - auto func = [&vault, &gatekeeper]() { + auto func = [&gatekeeper]() { gatekeeper.lock(); gatekeeper.unlock(); - auto unused = Singleton::get(&vault); + auto unused = SingletonConcurrency::get(); }; EXPECT_EQ(vault.livingSingletonCount(), 0); @@ -373,14 +408,18 @@ TEST(Singleton, SingletonConcurrency) { EXPECT_EQ(vault.livingSingletonCount(), 1); } +struct ConcurrencyStressTag {}; +template +using SingletonConcurrencyStress = Singleton ; + TEST(Singleton, SingletonConcurrencyStress) { - SingletonVault vault; - Singleton slowpoke_singleton(nullptr, nullptr, &vault); + auto& vault = *SingletonVault::singleton(); + SingletonConcurrencyStress slowpoke_singleton; std::vector ts; for (size_t i = 0; i < 100; ++i) { ts.emplace_back([&]() { - slowpoke_singleton.get_weak(&vault).lock(); + slowpoke_singleton.get_weak().lock(); }); } @@ -412,23 +451,28 @@ int* getNormalSingleton() { return &normal_singleton_value; } +struct MockTag {}; +template +using SingletonMock = Singleton ; + // Verify that existing Singleton's can be overridden // using the make_mock functionality. -TEST(Singleton, make_mock) { - SingletonVault vault(SingletonVault::Type::Strict); - Singleton watchdog_singleton(nullptr, nullptr, &vault); +TEST(Singleton, MockTest) { + auto& vault = *SingletonVault::singleton(); + + SingletonMock watchdog_singleton; 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::get(&vault)->serial_number; + int serial_count_first = SingletonMock::get()->serial_number; // Override existing mock using make_mock. - Singleton::make_mock(nullptr, nullptr, &vault); + SingletonMock::make_mock(); EXPECT_EQ(vault.registeredSingletonCount(), 1); - int serial_count_mock = Singleton::get(&vault)->serial_number; + int serial_count_mock = SingletonMock::get()->serial_number; // If serial_count value is the same, then singleton was not replaced. EXPECT_NE(serial_count_first, serial_count_mock); @@ -450,34 +494,25 @@ BENCHMARK_RELATIVE(MeyersSingleton, n) { } } -BENCHMARK_RELATIVE(FollySingletonSlow, n) { - SingletonVault benchmark_vault; - Singleton benchmark_singleton( - nullptr, nullptr, &benchmark_vault); - benchmark_vault.registrationComplete(); +struct BenchmarkTag {}; +template +using SingletonBenchmark = Singleton ; + +SingletonBenchmark benchmark_singleton; +BENCHMARK_RELATIVE(FollySingletonSlow, n) { for (size_t i = 0; i < n; ++i) { - doNotOptimizeAway(Singleton::get(&benchmark_vault)); + doNotOptimizeAway(SingletonBenchmark::get()); } } BENCHMARK_RELATIVE(FollySingletonFast, n) { - SingletonVault benchmark_vault; - Singleton 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 benchmark_singleton( - nullptr, nullptr, &benchmark_vault); - benchmark_vault.registrationComplete(); - for (size_t i = 0; i < n; ++i) { benchmark_singleton.get_weak_fast(); }