Commit 8f7c257f authored by Andrii Grynenko's avatar Andrii Grynenko Committed by Sara Golemon

Making each SingletonEntry a singleton

Summary:
Most of the singleton construction logic is moved to SingletonEntry, and each SingletonEntry is now also a singleton.
SingletonVault becomes only responsible for keeping singleton construction order (and potentially dependencies) and destoying them in correct order.
This also significantly improves perf of get() / get_weak() (not-fast)

This diff is based on D1823663.

Test Plan:
unit test

============================================================================
folly/experimental/test/SingletonTest.cpp       relative  time/iter  iters/s
============================================================================
NormalSingleton                                            333.35ps    3.00G
MeyersSingleton                                   99.99%   333.39ps    3.00G
FollySingletonSlow                                49.99%   666.84ps    1.50G
FollySingletonFast                                95.90%   347.61ps    2.88G
FollySingletonFastWeak                             2.22%    15.00ns   66.66M
============================================================================

Reviewed By: chip@fb.com

Subscribers: trunkagent, folly-diffs@, yfeldblum

FB internal diff: D1827390

Signature: t1:1827390:1423268514:da322d1dcaba54905d478b253f26dd76f890fb4e
parent a11dc9b7
......@@ -73,6 +73,7 @@ nobase_follyinclude_HEADERS = \
experimental/EventCount.h \
experimental/io/FsUtil.h \
experimental/Singleton.h \
experimental/Singleton-inl.h \
experimental/TestUtil.h \
FBString.h \
FBVector.h \
......
/*
* 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.
*/
namespace folly {
namespace detail {
template <typename T>
template <typename Tag, typename VaultTag>
SingletonHolder<T>& SingletonHolder<T>::singleton() {
static auto entry = new SingletonHolder<T>(
{typeid(T), typeid(Tag)},
*SingletonVault::singleton<VaultTag>());
return *entry;
}
template <typename T>
void SingletonHolder<T>::registerSingleton(CreateFunc c, TeardownFunc t) {
std::lock_guard<std::mutex> entry_lock(mutex_);
if (state_ != SingletonHolderState::NotRegistered) {
throw std::logic_error("Double registration");
}
create_ = std::move(c);
teardown_ = std::move(t);
state_ = SingletonHolderState::Dead;
}
template <typename T>
void SingletonHolder<T>::registerSingletonMock(CreateFunc c, TeardownFunc t) {
if (state_ == SingletonHolderState::NotRegistered) {
throw std::logic_error("Registering mock before singleton was registered");
}
destroyInstance();
std::lock_guard<std::mutex> entry_lock(mutex_);
create_ = std::move(c);
teardown_ = std::move(t);
}
template <typename T>
T* SingletonHolder<T>::get() {
if (LIKELY(state_ == SingletonHolderState::Living)) {
return instance_ptr_;
}
createInstance();
if (instance_weak_.expired()) {
throw std::runtime_error(
"Raw pointer to a singleton requested after its destruction.");
}
return instance_ptr_;
}
template <typename T>
std::weak_ptr<T> SingletonHolder<T>::get_weak() {
if (UNLIKELY(state_ != SingletonHolderState::Living)) {
createInstance();
}
return instance_weak_;
}
template <typename T>
TypeDescriptor SingletonHolder<T>::type() {
return type_;
}
template <typename T>
bool SingletonHolder<T>::hasLiveInstance() {
return state_ == SingletonHolderState::Living;
}
template <typename T>
void SingletonHolder<T>::destroyInstance() {
state_ = SingletonHolderState::Dead;
instance_.reset();
auto wait_result = destroy_baton_->timed_wait(
std::chrono::steady_clock::now() + kDestroyWaitTime);
if (!wait_result) {
LOG(ERROR) << "Singleton of type " << type_.name() << " has a "
<< "living reference at destroyInstances time; beware! Raw "
<< "pointer is " << instance_ptr_ << ". It is very likely "
<< "that some other singleton is holding a shared_ptr to it. "
<< "Make sure dependencies between these singletons are "
<< "properly defined.";
}
}
template <typename T>
SingletonHolder<T>::SingletonHolder(TypeDescriptor type__,
SingletonVault& vault) :
type_(type__), vault_(vault) {
}
template <typename T>
void SingletonHolder<T>::createInstance() {
// There's no synchronization here, so we may not see the current value
// for creating_thread if it was set by other thread, but we only care about
// it if it was set by current thread anyways.
if (creating_thread_ == std::this_thread::get_id()) {
throw std::out_of_range(std::string("circular singleton dependency: ") +
type_.name());
}
std::lock_guard<std::mutex> entry_lock(mutex_);
if (state_ == SingletonHolderState::Living) {
return;
}
if (state_ == SingletonHolderState::NotRegistered) {
throw std::out_of_range("Creating instance for unregistered singleton");
}
if (state_ == SingletonHolderState::Living) {
return;
}
creating_thread_ = std::this_thread::get_id();
RWSpinLock::ReadHolder rh(&vault_.stateMutex_);
if (vault_.state_ == SingletonVault::SingletonVaultState::Quiescing) {
creating_thread_ = std::thread::id();
return;
}
auto destroy_baton = std::make_shared<folly::Baton<>>();
auto teardown = teardown_;
// Can't use make_shared -- no support for a custom deleter, sadly.
instance_ = std::shared_ptr<T>(
create_(),
[destroy_baton, teardown](T* instance_ptr) mutable {
teardown(instance_ptr);
destroy_baton->post();
});
// We should schedule destroyInstances() only after the singleton was
// created. This will ensure it will be destroyed before singletons,
// not managed by folly::Singleton, which were initialized in its
// constructor
SingletonVault::scheduleDestroyInstances();
instance_weak_ = instance_;
instance_ptr_ = instance_.get();
creating_thread_ = std::thread::id();
destroy_baton_ = std::move(destroy_baton);
// This has to be the last step, because once state is Living other threads
// may access instance and instance_weak w/o synchronization.
state_.store(SingletonHolderState::Living);
{
RWSpinLock::WriteHolder wh(&vault_.mutex_);
vault_.creation_order_.push_back(type_);
}
}
}
}
......@@ -20,9 +20,9 @@
namespace folly {
namespace {
namespace detail {
static constexpr std::chrono::seconds kDestroyWaitTime{5};
constexpr std::chrono::seconds SingletonHolderBase::kDestroyWaitTime;
}
......@@ -46,7 +46,7 @@ void SingletonVault::destroyInstances() {
for (auto type_iter = creation_order_.rbegin();
type_iter != creation_order_.rend();
++type_iter) {
destroyInstance(singletons_.find(*type_iter));
singletons_[*type_iter]->destroyInstance();
}
}
......@@ -56,28 +56,6 @@ 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(SingletonMap::iterator entry_it) {
const auto& type = entry_it->first;
auto& entry = *(entry_it->second);
entry.state = detail::SingletonEntryState::Dead;
entry.instance.reset();
auto wait_result = entry.destroy_baton->timed_wait(
std::chrono::steady_clock::now() + kDestroyWaitTime);
if (!wait_result) {
LOG(ERROR) << "Singleton of type " << type.prettyName() << " has a living "
<< "reference at destroyInstances time; beware! Raw pointer "
<< "is " << entry.instance_ptr << ". It is very likely that "
<< "some other singleton is holding a shared_ptr to it. Make "
<< "sure dependencies between these singletons are properly "
<< "defined.";
}
}
void SingletonVault::reenableInstances() {
RWSpinLock::WriteHolder state_wh(&stateMutex_);
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment