X-Git-Url: http://demsky.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Fexperimental%2FSingleton.h;h=7af17256337cafed16e53bbc31c61e645ee6ef7e;hb=f8ff6ac066f98190941687aca357cba0985daf9c;hp=c861e1bc18cdb11110c401facf1bcf9ff74660df;hpb=be42394a1a5aae9689a7a02b7d9e9333829f37e6;p=folly.git diff --git a/folly/experimental/Singleton.h b/folly/experimental/Singleton.h index c861e1bc..7af17256 100644 --- a/folly/experimental/Singleton.h +++ b/folly/experimental/Singleton.h @@ -1,5 +1,5 @@ /* - * Copyright 2014 Facebook, Inc. + * 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. @@ -48,15 +48,19 @@ // circular dependency, a runtime error will occur. // // You can have multiple singletons of the same underlying type, but -// each must be given a unique name: +// each must be given a unique tag. If no tag is specified - default tag is used // // namespace { -// folly::Singleton s1("name1"); -// folly::Singleton s2("name2"); +// struct Tag1 {}; +// struct Tag2 {}; +// folly::Singleton s_default(); +// folly::Singleton s1(); +// folly::Singleton s2(); // } // ... -// MyExpensiveService* svc1 = Singleton::get("name1"); -// MyExpensiveService* svc2 = Singleton::get("name2"); +// MyExpensiveService* svc_default = s_default.get(); +// MyExpensiveService* svc1 = s1.get(); +// MyExpensiveService* svc2 = s2.get(); // // By default, the singleton instance is constructed via new and // deleted via delete, but this is configurable: @@ -74,11 +78,20 @@ // TeardownFunc for each singleton, in the reverse order they were // created. It is your responsibility to ensure your singletons can // handle cases where the singletons they depend on go away, however. +// Singletons won't be recreated after destroyInstances call. If you +// want to re-enable singleton creation (say after fork was called) you +// should call reenableInstances. #pragma once +#include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -114,124 +127,224 @@ namespace folly { // ensure registrationComplete() is called near the top of your main() // function, otherwise no singletons can be instantiated. +class SingletonVault; + namespace detail { -const char* const kDefaultTypeDescriptorName = "(default)"; +struct DefaultTag {}; + // A TypeDescriptor is the unique handle for a given singleton. It is // a combinaiton of the type and of the optional name, and is used as // a key in unordered_maps. class TypeDescriptor { public: - TypeDescriptor(const std::type_info& ti, std::string name) - : ti_(ti), name_(name) { - if (name_ == kDefaultTypeDescriptorName) { - LOG(DFATAL) << "Caller used the default name as their literal name; " - << "name your singleton something other than " - << kDefaultTypeDescriptorName; + TypeDescriptor(const std::type_info& ti, + const std::type_info& tag_ti) + : ti_(ti), tag_ti_(tag_ti) { + } + + TypeDescriptor(const TypeDescriptor& other) + : ti_(other.ti_), tag_ti_(other.tag_ti_) { + } + + TypeDescriptor& operator=(const TypeDescriptor& other) { + if (this != &other) { + ti_ = other.ti_; + tag_ti_ = other.tag_ti_; } + + return *this; } std::string name() const { - std::string ret = ti_.name(); - ret += "/"; - if (name_.empty()) { - ret += kDefaultTypeDescriptorName; - } else { - ret += name_; + auto ret = demangle(ti_.name()); + if (tag_ti_ != std::type_index(typeid(DefaultTag))) { + ret += "/"; + ret += demangle(tag_ti_.name()); } - return ret; + return ret.toStdString(); } friend class TypeDescriptorHasher; bool operator==(const TypeDescriptor& other) const { - return ti_ == other.ti_ && name_ == other.name_; + return ti_ == other.ti_ && tag_ti_ == other.tag_ti_; } private: - const std::type_index ti_; - const std::string name_; + std::type_index ti_; + std::type_index tag_ti_; }; class TypeDescriptorHasher { public: size_t operator()(const TypeDescriptor& ti) const { - return folly::hash::hash_combine(ti.ti_, ti.name_); + return folly::hash::hash_combine(ti.ti_, ti.tag_ti_); } }; + +// This interface is used by SingletonVault to interact with SingletonHolders. +// Having a non-template interface allows SingletonVault to keep a list of all +// SingletonHolders. +class SingletonHolderBase { + public: + virtual ~SingletonHolderBase() {} + + virtual TypeDescriptor type() = 0; + virtual bool hasLiveInstance() = 0; + virtual void destroyInstance() = 0; + + protected: + static constexpr std::chrono::seconds kDestroyWaitTime{5}; +}; + +// An actual instance of a singleton, tracking the instance itself, +// its state as described above, and the create and teardown +// functions. +template +struct SingletonHolder : public SingletonHolderBase { + public: + typedef std::function TeardownFunc; + typedef std::function CreateFunc; + + template + inline static SingletonHolder& singleton(); + + inline T* get(); + inline std::weak_ptr get_weak(); + + void registerSingleton(CreateFunc c, TeardownFunc t); + void registerSingletonMock(CreateFunc c, TeardownFunc t); + virtual TypeDescriptor type(); + virtual bool hasLiveInstance(); + virtual void destroyInstance(); + + private: + SingletonHolder(TypeDescriptor type, SingletonVault& vault); + + void createInstance(); + + enum class SingletonHolderState { + NotRegistered, + Dead, + Living, + }; + + TypeDescriptor type_; + SingletonVault& vault_; + + // mutex protects the entire entry during construction/destruction + std::mutex mutex_; + + // State of the singleton entry. If state is Living, instance_ptr and + // instance_weak can be safely accessed w/o synchronization. + std::atomic state_{SingletonHolderState::NotRegistered}; + + // the thread creating the singleton (only valid while creating an object) + std::thread::id creating_thread_; + + // The singleton itself and related functions. + + // holds a shared_ptr to singleton instance, set when state is changed from + // Dead to Living. Reset when state is changed from Living to Dead. + std::shared_ptr instance_; + // weak_ptr to the singleton instance, set when state is changed from Dead + // to Living. We never write to this object after initialization, so it is + // safe to read it from different threads w/o synchronization if we know + // that state is set to Living + std::weak_ptr instance_weak_; + // Time we wait on destroy_baton after releasing Singleton shared_ptr. + std::shared_ptr> destroy_baton_; + T* instance_ptr_ = nullptr; + CreateFunc create_ = nullptr; + TeardownFunc teardown_ = nullptr; + + std::shared_ptr> print_destructor_stack_trace_; + + SingletonHolder(const SingletonHolder&) = delete; + SingletonHolder& operator=(const SingletonHolder&) = delete; + SingletonHolder& operator=(SingletonHolder&&) = delete; + SingletonHolder(SingletonHolder&&) = delete; +}; + } class SingletonVault { public: - SingletonVault() {}; + enum class Type { Strict, Relaxed }; + + explicit SingletonVault(Type type = Type::Relaxed) : type_(type) {} + + // Destructor is only called by unit tests to check destroyInstances. ~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, - TeardownFunc teardown) { - std::lock_guard guard(mutex_); - - CHECK_THROW(state_ == SingletonVaultState::Registering, std::logic_error); - CHECK_THROW(singletons_.find(type) == singletons_.end(), std::logic_error); - auto& entry = singletons_[type]; - if (!entry) { - entry.reset(new SingletonEntry); + void registerSingleton(detail::SingletonHolderBase* entry) { + RWSpinLock::ReadHolder rh(&stateMutex_); + + stateCheck(SingletonVaultState::Running); + + if (UNLIKELY(registrationComplete_)) { + throw std::logic_error( + "Registering singleton after registrationComplete()."); } - std::lock_guard entry_guard(entry->mutex); - CHECK(entry->instance == nullptr); - CHECK(create); - CHECK(teardown); - entry->create = create; - entry->teardown = teardown; - entry->state = SingletonEntryState::Dead; + RWSpinLock::ReadHolder rhMutex(&mutex_); + CHECK_THROW(singletons_.find(entry->type()) == singletons_.end(), + std::logic_error); + + RWSpinLock::UpgradedHolder wh(&mutex_); + singletons_[entry->type()] = entry; } // Mark registration is complete; no more singletons can be // registered at this point. void registrationComplete() { - std::lock_guard guard(mutex_); - CHECK_THROW(state_ == SingletonVaultState::Registering, std::logic_error); - state_ = SingletonVaultState::Running; - } + RequestContext::getStaticContext(); + std::atexit([](){ SingletonVault::singleton()->destroyInstances(); }); - // Destroy all singletons; when complete, the vault can create - // singletons once again, or remain dormant. - void destroyInstances(); + RWSpinLock::WriteHolder wh(&stateMutex_); - // Retrieve a singleton from the vault, creating it if necessary. - std::shared_ptr get_shared(detail::TypeDescriptor type) { - std::unique_lock lock(mutex_); - auto entry = get_entry(type, &lock); - return entry->instance; - } + 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."); + } + } + } - // This function is inherently racy since we don't hold the - // shared_ptr that contains the Singleton. It is the caller's - // responsibility to be sane with this, but it is preferable to use - // the weak_ptr interface for true safety. - void* get_ptr(detail::TypeDescriptor type) { - std::unique_lock lock(mutex_); - auto entry = get_entry(type, &lock); - return entry->instance_ptr; + registrationComplete_ = true; } + // Destroy all singletons; when complete, the vault can't create + // singletons once again until reenableInstances() is called. + void destroyInstances(); + + // Enable re-creating singletons after destroyInstances() was called. + void reenableInstances(); + // For testing; how many registered and living singletons we have. size_t registeredSingletonCount() const { - std::lock_guard guard(mutex_); + RWSpinLock::ReadHolder rh(&mutex_); + return singletons_.size(); } size_t livingSingletonCount() const { - std::lock_guard guard(mutex_); + RWSpinLock::ReadHolder rh(&mutex_); + size_t ret = 0; for (const auto& p : singletons_) { - if (p.second->instance) { + if (p.second->hasLiveInstance()) { ++ret; } } @@ -241,122 +354,69 @@ 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; + } + + typedef std::string(*StackTraceGetterPtr)(); + + static std::atomic& stackTraceGetter() { + static std::atomic stackTraceGetterPtr; + return stackTraceGetterPtr; + } private: + template + friend class detail::SingletonHolder; + // The two stages of life for a vault, as mentioned in the class comment. enum class SingletonVaultState { - Registering, Running, + Quiescing, }; - // Each singleton in the vault can be in three states: dead - // (registered but never created), being born (running the - // CreateFunc), and living (CreateFunc returned an instance). - enum class SingletonEntryState { - Dead, - BeingBorn, - Living, - }; - - // An actual instance of a singleton, tracking the instance itself, - // its state as described above, and the create and teardown - // functions. - struct SingletonEntry { - // mutex protects the entire entry - std::mutex mutex; - - // state changes notify state_condvar - SingletonEntryState state = SingletonEntryState::Dead; - std::condition_variable state_condvar; - - // the thread creating the singleton - std::thread::id creating_thread; - - // The singleton itself and related functions. - std::shared_ptr instance; - void* instance_ptr = nullptr; - CreateFunc create = nullptr; - TeardownFunc teardown = nullptr; - - SingletonEntry() = default; - SingletonEntry(const SingletonEntry&) = delete; - SingletonEntry& operator=(const SingletonEntry&) = delete; - SingletonEntry& operator=(SingletonEntry&&) = delete; - SingletonEntry(SingletonEntry&&) = delete; - }; + // Each singleton in the vault can be in two states: dead + // (registered but never created), living (CreateFunc returned an instance). - // Get a pointer to the living SingletonEntry for the specified - // type. The singleton is created as part of this function, if - // necessary. - SingletonEntry* get_entry(detail::TypeDescriptor type, - std::unique_lock* lock) { - // mutex must be held when calling this function - if (state_ != SingletonVaultState::Running) { - throw std::logic_error( - "Attempt to load a singleton before " - "SingletonVault::registrationComplete was called (hint: you probably " - "didn't call initFacebook)"); + void stateCheck(SingletonVaultState expected, + const char* msg="Unexpected singleton state change") { + if (expected != state_) { + throw std::logic_error(msg); } - - auto it = singletons_.find(type); - if (it == singletons_.end()) { - throw std::out_of_range(std::string("non-existent singleton: ") + - type.name()); - } - - auto entry = it->second.get(); - std::unique_lock entry_lock(entry->mutex); - - if (entry->state == SingletonEntryState::BeingBorn) { - // If this thread is trying to give birth to the singleton, it's - // a circular dependency and we must panic. - if (entry->creating_thread == std::this_thread::get_id()) { - throw std::out_of_range(std::string("circular singleton dependency: ") + - type.name()); - } - - // Otherwise, another thread is constructing the singleton; - // let's wait on a condvar to see it complete. We release and - // reaquire lock while waiting on the entry to resolve its state. - lock->unlock(); - entry->state_condvar.wait(entry_lock, [&entry]() { - return entry->state != SingletonEntryState::BeingBorn; - }); - lock->lock(); - } - - if (entry->instance == nullptr) { - CHECK(entry->state == SingletonEntryState::Dead); - entry->state = SingletonEntryState::BeingBorn; - entry->creating_thread = std::this_thread::get_id(); - - entry_lock.unlock(); - lock->unlock(); - // Can't use make_shared -- no support for a custom deleter, sadly. - auto instance = std::shared_ptr(entry->create(), entry->teardown); - lock->lock(); - entry_lock.lock(); - - CHECK(entry->state == SingletonEntryState::BeingBorn); - entry->instance = instance; - entry->instance_ptr = instance.get(); - entry->state = SingletonEntryState::Living; - entry->state_condvar.notify_all(); - - creation_order_.push_back(type); - } - CHECK(entry->state == SingletonEntryState::Living); - return entry; } - mutable std::mutex mutex_; - typedef std::unique_ptr SingletonEntryPtr; - std::unordered_map singletons_; + // This method only matters if registrationComplete() is never called. + // Otherwise destroyInstances is scheduled to be executed atexit. + // + // Initializes static object, which calls destroyInstances on destruction. + // Used to have better deletion ordering with singleton not managed by + // folly::Singleton. The desruction will happen in the following order: + // 1. Singletons, not managed by folly::Singleton, which were created after + // any of the singletons managed by folly::Singleton was requested. + // 2. All singletons managed by folly::Singleton + // 3. Singletons, not managed by folly::Singleton, which were created before + // any of the singletons managed by folly::Singleton was requested. + static void scheduleDestroyInstances(); + + typedef std::unordered_map SingletonMap; + + mutable folly::RWSpinLock mutex_; + SingletonMap singletons_; std::vector creation_order_; - SingletonVaultState state_ = SingletonVaultState::Registering; + SingletonVaultState state_{SingletonVaultState::Running}; + bool registrationComplete_{false}; + folly::RWSpinLock stateMutex_; + Type type_{Type::Relaxed}; }; // This is the wrapper class that most users actually interact with. @@ -364,7 +424,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; @@ -373,97 +435,83 @@ 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 */) { - return get_ptr({typeid(T), ""}, vault); - } - - static T* get(const char* name, - SingletonVault* vault = nullptr /* for testing */) { - return get_ptr({typeid(T), name}, vault); + static T* get() { + return getEntry().get(); } // If, however, you do need to hold a reference to the specific // 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 */) { - return get_weak("", vault); + static std::weak_ptr get_weak() { + return getEntry().get_weak(); } - static std::weak_ptr get_weak( - const char* name, SingletonVault* vault = nullptr /* for testing */) { - return std::weak_ptr(get_shared({typeid(T), name}, vault)); - } + // Allow the Singleton instance to also retrieve the underlying + // singleton, if desired. + T& operator*() { return *get(); } + T* operator->() { return get(); } - std::weak_ptr get_weak(const char* name) { - return std::weak_ptr(get_shared({typeid(T), name}, vault_)); + explicit Singleton(std::nullptr_t _ = nullptr, + Singleton::TeardownFunc t = nullptr) : + Singleton ([]() { return new T; }, std::move(t)) { } - // Allow the Singleton instance to also retrieve the underlying - // singleton, if desired. - T* ptr() { return get_ptr(type_descriptor_, vault_); } - T& operator*() { return *ptr(); } - T* operator->() { return ptr(); } + explicit Singleton(Singleton::CreateFunc c, + Singleton::TeardownFunc t = nullptr) { + if (c == nullptr) { + throw std::logic_error( + "nullptr_t should be passed if you want T to be default constructed"); + } - explicit Singleton(Singleton::CreateFunc c = nullptr, - Singleton::TeardownFunc t = nullptr, - SingletonVault* vault = nullptr /* for testing */) - : Singleton({typeid(T), ""}, c, t, vault) {} + auto vault = SingletonVault::singleton(); + getEntry().registerSingleton(std::move(c), getTeardownFunc(std::move(t))); + vault->registerSingleton(&getEntry()); + } - explicit Singleton(const char* name, - Singleton::CreateFunc c = nullptr, - Singleton::TeardownFunc t = nullptr, - 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. + * 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. + */ + static void make_mock(std::nullptr_t c = nullptr, + typename Singleton::TeardownFunc t = nullptr) { + make_mock([]() { return new T; }, t); + } - private: - explicit Singleton(detail::TypeDescriptor type, - Singleton::CreateFunc c = nullptr, - Singleton::TeardownFunc t = nullptr, - SingletonVault* vault = nullptr /* for testing */) - : type_descriptor_(type) { + static void make_mock(CreateFunc c, + typename Singleton::TeardownFunc t = nullptr) { if (c == nullptr) { - c = []() { return new T; }; - } - SingletonVault::TeardownFunc teardown; - if (t == nullptr) { - teardown = [](void* v) { delete static_cast(v); }; - } else { - teardown = [t](void* v) { t(static_cast(v)); }; + 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; - vault->registerSingleton(type, c, teardown); - } + auto& entry = getEntry(); - static T* get_ptr(detail::TypeDescriptor type_descriptor = {typeid(T), ""}, - SingletonVault* vault = nullptr /* for testing */) { - return static_cast( - (vault ?: SingletonVault::singleton())->get_ptr(type_descriptor)); + entry.registerSingletonMock(c, getTeardownFunc(t)); } - // Don't use this function, it's private for a reason! Using it - // would defeat the *entire purpose* of the vault in that we lose - // the ability to guarantee that, after a destroyInstances is - // called, all instances are, in fact, destroyed. You should use - // weak_ptr if you need to hold a reference to the singleton and - // guarantee briefly that it exists. - // - // Yes, you can just get the weak pointer and lock it, but hopefully - // if you have taken the time to read this far, you see why that - // would be bad. - static std::shared_ptr get_shared( - detail::TypeDescriptor type_descriptor = {typeid(T), ""}, - SingletonVault* vault = nullptr /* for testing */) { - return std::static_pointer_cast( - (vault ?: SingletonVault::singleton())->get_shared(type_descriptor)); + private: + inline static detail::SingletonHolder& getEntry() { + return detail::SingletonHolder::template singleton(); } - detail::TypeDescriptor type_descriptor_; - SingletonVault* vault_; + // Construct TeardownFunc. + static typename detail::SingletonHolder::TeardownFunc getTeardownFunc( + TeardownFunc t) { + if (t == nullptr) { + return [](T* v) { delete v; }; + } else { + return t; + } + } }; + } + +#include