From: Adrian Hamza Date: Wed, 10 Dec 2014 17:43:25 +0000 (-0800) Subject: Add mock singleton injection support to folly/experimental Singleton (t5653148). X-Git-Tag: v0.22.0~116 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=accea1ae7213b528a91110ced5248d16ca5f3b11;p=folly.git Add mock singleton injection support to folly/experimental Singleton (t5653148). Summary: Add mock singleton injection support to folly/experimental Singleton (t5653148). Test Plan: Added unit tests for MockSingleton, all unit tests passed. Validated this works for my scenario. Reviewed By: andrii@fb.com Subscribers: trunkagent, njormrod, folly-diffs@ FB internal diff: D1690946 Tasks: 5653148 Signature: t1:1690946:1418080331:948d7051a5e5a2653dc393c123f188c56072c6db --- diff --git a/folly/experimental/Singleton.cpp b/folly/experimental/Singleton.cpp index 5c1e650e..dc032d3a 100644 --- a/folly/experimental/Singleton.cpp +++ b/folly/experimental/Singleton.cpp @@ -41,19 +41,7 @@ void SingletonVault::destroyInstances() { 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 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; - entry->state_condvar.notify_all(); + destroyInstance(type); } } @@ -63,6 +51,28 @@ void SingletonVault::destroyInstances() { } } +/* Destroy and clean-up one singleton. Must be invoked while holding + * a read lock on mutex_. + * @param typeDescriptor - the type key for the removed singleton. + */ +void SingletonVault::destroyInstance( + const detail::TypeDescriptor& typeDescriptor) { + auto it = singletons_.find(typeDescriptor); + CHECK(it != singletons_.end()); + auto& entry = it->second; + std::lock_guard entry_guard(entry->mutex); + if (entry->instance.use_count() > 1) { + LOG(ERROR) << "Singleton of typeDescriptor " + << typeDescriptor.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; + entry->state_condvar.notify_all(); +} + void SingletonVault::reenableInstances() { RWSpinLock::WriteHolder state_wh(&stateMutex_); diff --git a/folly/experimental/Singleton.h b/folly/experimental/Singleton.h index 20416015..2e342077 100644 --- a/folly/experimental/Singleton.h +++ b/folly/experimental/Singleton.h @@ -83,6 +83,7 @@ #include #include +#include #include #include #include @@ -135,6 +136,19 @@ class TypeDescriptor { } } + TypeDescriptor(const TypeDescriptor& other) + : ti_(other.ti_), name_(other.name_) { + } + + TypeDescriptor& operator=(const TypeDescriptor& other) { + if (this != &other) { + name_ = other.name_; + ti_ = other.ti_; + } + + return *this; + } + std::string name() const { std::string ret = ti_.name(); ret += "/"; @@ -153,8 +167,8 @@ class TypeDescriptor { } private: - const std::type_index ti_; - const std::string name_; + std::type_index ti_; + std::string name_; }; class TypeDescriptorHasher { @@ -177,7 +191,9 @@ class SingletonVault { typedef std::function TeardownFunc; typedef std::function CreateFunc; - // Register a singleton of a given type with the create and teardown + // Ensure that Singleton has not been registered previously and that + // registration is not complete. If validations succeeds, + // register a singleton of a given type with the create and teardown // functions. void registerSingleton(detail::TypeDescriptor type, CreateFunc create, @@ -185,16 +201,30 @@ class SingletonVault { RWSpinLock::ReadHolder rh(&stateMutex_); stateCheck(SingletonVaultState::Running); + if (UNLIKELY(registrationComplete_)) { throw std::logic_error( "Registering singleton after registrationComplete()."); } - RWSpinLock::WriteHolder wh(&mutex_); - + RWSpinLock::ReadHolder rhMutex(&mutex_); CHECK_THROW(singletons_.find(type) == singletons_.end(), std::logic_error); + + registerSingletonImpl(type, create, teardown); + } + + // Register a singleton of a given type with the create and teardown + // functions. Must hold reader locks on stateMutex_ and mutex_ + // when invoking this function. + void registerSingletonImpl(detail::TypeDescriptor type, + CreateFunc create, + TeardownFunc teardown) { + RWSpinLock::UpgradedHolder wh(&mutex_); + auto& entry = singletons_[type]; - entry.reset(new SingletonEntry); + if (!entry) { + entry.reset(new SingletonEntry); + } std::lock_guard entry_guard(entry->mutex); CHECK(entry->instance == nullptr); @@ -205,6 +235,43 @@ class SingletonVault { entry->state = SingletonEntryState::Dead; } + /* Register a mock singleton used for testing of singletons which + * depend on other private singletons which cannot be otherwise injected. + */ + void registerMockSingleton(detail::TypeDescriptor type, + CreateFunc create, + TeardownFunc teardown) { + RWSpinLock::ReadHolder rh(&stateMutex_); + RWSpinLock::ReadHolder rhMutex(&mutex_); + + auto existing_entry_it = singletons_.find(type); + // Mock singleton registration, we allow existing entry to be overridden. + if (existing_entry_it != singletons_.end()) { + // Upgrade to write lock. + RWSpinLock::UpgradedHolder whMutex(&mutex_); + + // Destroy existing singleton. + destroyInstance(type); + + // Remove singleton from creation order and singletons_. + // This happens only in test code and not frequently. + // Performance is not a concern here. + auto creation_order_it = std::find( + creation_order_.begin(), + creation_order_.end(), + type); + if (creation_order_it != creation_order_.end()) { + creation_order_.erase(creation_order_it); + } + } + + // This method will re-upgrade to write lock for &mutex_. + registerSingletonImpl( + type, + create, + teardown); + } + // Mark registration is complete; no more singletons can be // registered at this point. void registrationComplete() { @@ -231,6 +298,12 @@ class SingletonVault { // singletons once again until reenableInstances() is called. void destroyInstances(); + /* Destroy and clean-up one singleton. Must be invoked while holding + * a read lock on mutex_. + * @param typeDescriptor - the type key for the removed singleton. + */ + void destroyInstance(const detail::TypeDescriptor& typeDescriptor); + // Enable re-creating singletons after destroyInstances() was called. void reenableInstances(); @@ -491,26 +564,87 @@ class Singleton { SingletonVault* vault = nullptr /* for testing */) : Singleton({typeid(T), name}, c, t, vault) {} + /** + * Construct and inject a mock singleton which should be used only from tests. + * See overloaded method for more details. + */ + template + static void make_mock(CreateFunc c = nullptr, + typename Singleton::TeardownFunc t = nullptr, + SingletonVault* vault = nullptr /* for testing */) { + + make_mock("", c, t, vault); + } + + /** + * Construct and inject a mock singleton which should be used only from tests. + * Unlike regular singletons which are initialized once per process lifetime, + * mock singletons live for the duration of a test. This means that one process + * running multiple tests can initialize and register the same singleton + * multiple times. This functionality should be used only from tests + * since it relaxes validation and performance in order to be able to perform + * the injection. The returned mock singleton is functionality identical to + * regular singletons. + */ + template + static void make_mock(const char* name, + CreateFunc c = nullptr, + typename Singleton::TeardownFunc t = nullptr, + SingletonVault* vault = nullptr /* for testing */ ) { + + Singleton mockSingleton({typeid(T), name}, c, t, vault, false); + mockSingleton.vault_->registerMockSingleton( + mockSingleton.type_descriptor_, + c, + getTeardownFunc(t)); + } + private: explicit Singleton(detail::TypeDescriptor type, std::nullptr_t, Singleton::TeardownFunc t, - SingletonVault* vault) : + SingletonVault* vault, + bool registerSingleton = true) : Singleton (type, []() { return new T; }, std::move(t), - vault) { + vault, + registerSingleton) { } explicit Singleton(detail::TypeDescriptor type, Singleton::CreateFunc c, Singleton::TeardownFunc t, - SingletonVault* vault) + SingletonVault* vault, + bool registerSingleton = true) : type_descriptor_(type) { 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(); + } + + vault_ = vault; + if (registerSingleton) { + vault->registerSingleton(type, c, getTeardownFunc(t)); + } + } + + static inline void make_mock(const char* name, + std::nullptr_t c, + typename Singleton::TeardownFunc t = nullptr, + SingletonVault* vault = nullptr /* for testing */ ) { + make_mock(name, []() { return new T; }, std::move(t), vault); + } + + +private: + // Construct SingletonVault::TeardownFunc. + static SingletonVault::TeardownFunc getTeardownFunc( + Singleton::TeardownFunc t) { SingletonVault::TeardownFunc teardown; if (t == nullptr) { teardown = [](void* v) { delete static_cast(v); }; @@ -518,11 +652,7 @@ class Singleton { teardown = [t](void* v) { t(static_cast(v)); }; } - if (vault == nullptr) { - vault = SingletonVault::singleton(); - } - vault_ = vault; - vault->registerSingleton(type, c, teardown); + return teardown; } static T* get_ptr(detail::TypeDescriptor type_descriptor = {typeid(T), ""}, @@ -551,4 +681,5 @@ class Singleton { detail::TypeDescriptor type_descriptor_; SingletonVault* vault_; }; + } diff --git a/folly/experimental/test/SingletonTest.cpp b/folly/experimental/test/SingletonTest.cpp index 821a41cb..38a7598a 100644 --- a/folly/experimental/test/SingletonTest.cpp +++ b/folly/experimental/test/SingletonTest.cpp @@ -362,6 +362,28 @@ int* getNormalSingleton() { 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_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::get(&vault)->serial_number; + + // Override existing mock using make_mock. + Singleton::make_mock(nullptr, nullptr, &vault); + + EXPECT_EQ(vault.registeredSingletonCount(), 1); + int serial_count_mock = Singleton::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; };