Commit e9a78a18 authored by Andrey Obraztsov's avatar Andrey Obraztsov Committed by facebook-github-bot-4

Replace Singleton<T>::get() with Singleton<T>::try_get and make it obsolete

Summary: BREAKING CHANGE! Deprecate Singleton::get() and replace it with Singleton::try_get() that return smart pointer that allows to manage lifetime of a reference and prevents it from being deleted if the reference is still in use

Reviewed By: @chipturner

Differential Revision: D2268791
parent 2f107c8a
...@@ -104,6 +104,11 @@ ...@@ -104,6 +104,11 @@
#include <glog/logging.h> #include <glog/logging.h>
// use this guard to handleSingleton breaking change in 3rd party code
#ifndef FOLLY_SINGLETON_TRY_GET
#define FOLLY_SINGLETON_TRY_GET
#endif
namespace folly { namespace folly {
// For actual usage, please see the Singleton<T> class at the bottom // For actual usage, please see the Singleton<T> class at the bottom
...@@ -423,7 +428,7 @@ class SingletonVault { ...@@ -423,7 +428,7 @@ class SingletonVault {
// It allows for simple access to registering and instantiating // It allows for simple access to registering and instantiating
// singletons. Create instances of this class in the global scope of // singletons. Create instances of this class in the global scope of
// type Singleton<T> to register your singleton for later access via // type Singleton<T> to register your singleton for later access via
// Singleton<T>::get(). // Singleton<T>::try_get().
template <typename T, template <typename T,
typename Tag = detail::DefaultTag, typename Tag = detail::DefaultTag,
typename VaultTag = detail::DefaultTag /* for testing */> typename VaultTag = detail::DefaultTag /* for testing */>
...@@ -435,7 +440,7 @@ class Singleton { ...@@ -435,7 +440,7 @@ class Singleton {
// Generally your program life cycle should be fine with calling // Generally your program life cycle should be fine with calling
// get() repeatedly rather than saving the reference, and then not // get() repeatedly rather than saving the reference, and then not
// call get() during process shutdown. // call get() during process shutdown.
static T* get() { static T* get() __attribute__ ((__deprecated__("Replaced by try_get"))) {
return getEntry().get(); return getEntry().get();
} }
...@@ -447,10 +452,20 @@ class Singleton { ...@@ -447,10 +452,20 @@ class Singleton {
return getEntry().get_weak(); return getEntry().get_weak();
} }
// Allow the Singleton<t> instance to also retrieve the underlying // Preferred alternative to get_weak, it returns shared_ptr that can be
// singleton, if desired. // stored; a singleton won't be destroyed unless shared_ptr is destroyed.
T& operator*() { return *get(); } // Avoid holding these shared_ptrs beyond the scope of a function;
T* operator->() { return get(); } // don't put them in member variables, always use try_get() instead
static std::shared_ptr<T> try_get() {
auto ret = get_weak().lock();
if (!ret) {
LOG(DFATAL) <<
"folly::Singleton<" << getEntry().type().name() <<
">::get_weak() called on destructed singleton; "
"returning nullptr, possible segfault coming";
}
return ret;
}
explicit Singleton(std::nullptr_t _ = nullptr, explicit Singleton(std::nullptr_t _ = nullptr,
typename Singleton::TeardownFunc t = nullptr) : typename Singleton::TeardownFunc t = nullptr) :
......
...@@ -67,19 +67,24 @@ TEST(Singleton, BasicGlobalUsage) { ...@@ -67,19 +67,24 @@ TEST(Singleton, BasicGlobalUsage) {
EXPECT_EQ(Watchdog::creation_order.size(), 0); EXPECT_EQ(Watchdog::creation_order.size(), 0);
EXPECT_EQ(SingletonVault::singleton()->registeredSingletonCount(), 1); EXPECT_EQ(SingletonVault::singleton()->registeredSingletonCount(), 1);
EXPECT_EQ(SingletonVault::singleton()->livingSingletonCount(), 0); EXPECT_EQ(SingletonVault::singleton()->livingSingletonCount(), 0);
auto wd1 = Singleton<GlobalWatchdog>::get();
{
std::shared_ptr<GlobalWatchdog> wd1 = Singleton<GlobalWatchdog>::try_get();
EXPECT_NE(wd1, nullptr); EXPECT_NE(wd1, nullptr);
EXPECT_EQ(Watchdog::creation_order.size(), 1); EXPECT_EQ(Watchdog::creation_order.size(), 1);
auto wd2 = Singleton<GlobalWatchdog>::get(); std::shared_ptr<GlobalWatchdog> wd2 = Singleton<GlobalWatchdog>::try_get();
EXPECT_NE(wd2, nullptr); EXPECT_NE(wd2, nullptr);
EXPECT_EQ(wd1, wd2); EXPECT_EQ(wd1.get(), wd2.get());
EXPECT_EQ(Watchdog::creation_order.size(), 1); EXPECT_EQ(Watchdog::creation_order.size(), 1);
}
SingletonVault::singleton()->destroyInstances(); SingletonVault::singleton()->destroyInstances();
EXPECT_EQ(Watchdog::creation_order.size(), 0); EXPECT_EQ(Watchdog::creation_order.size(), 0);
} }
TEST(Singleton, MissingSingleton) { TEST(Singleton, MissingSingleton) {
EXPECT_DEATH([]() { auto u = Singleton<UnregisteredWatchdog>::get(); }(), ""); EXPECT_DEATH([]() { auto u = Singleton<UnregisteredWatchdog>::try_get(); }(),
"");
} }
struct BasicUsageTag {}; struct BasicUsageTag {};
...@@ -101,20 +106,24 @@ TEST(Singleton, BasicUsage) { ...@@ -101,20 +106,24 @@ TEST(Singleton, BasicUsage) {
vault.registrationComplete(); vault.registrationComplete();
Watchdog* s1 = SingletonBasicUsage<Watchdog>::get(); // limit a scope to release references so we can destroy them later
{
std::shared_ptr<Watchdog> s1 = SingletonBasicUsage<Watchdog>::try_get();
EXPECT_NE(s1, nullptr); EXPECT_NE(s1, nullptr);
Watchdog* s2 = SingletonBasicUsage<Watchdog>::get(); std::shared_ptr<Watchdog> s2 = SingletonBasicUsage<Watchdog>::try_get();
EXPECT_NE(s2, nullptr); EXPECT_NE(s2, nullptr);
EXPECT_EQ(s1, s2); EXPECT_EQ(s1, s2);
auto s3 = SingletonBasicUsage<ChildWatchdog>::get(); std::shared_ptr<ChildWatchdog> s3 =
SingletonBasicUsage<ChildWatchdog>::try_get();
EXPECT_NE(s3, nullptr); EXPECT_NE(s3, nullptr);
EXPECT_NE(s2, s3); EXPECT_NE(s2, s3);
EXPECT_EQ(vault.registeredSingletonCount(), 2); EXPECT_EQ(vault.registeredSingletonCount(), 2);
EXPECT_EQ(vault.livingSingletonCount(), 2); EXPECT_EQ(vault.livingSingletonCount(), 2);
}
vault.destroyInstances(); vault.destroyInstances();
EXPECT_EQ(vault.registeredSingletonCount(), 2); EXPECT_EQ(vault.registeredSingletonCount(), 2);
...@@ -138,11 +147,10 @@ TEST(Singleton, DirectUsage) { ...@@ -138,11 +147,10 @@ TEST(Singleton, DirectUsage) {
EXPECT_EQ(vault.registeredSingletonCount(), 2); EXPECT_EQ(vault.registeredSingletonCount(), 2);
vault.registrationComplete(); vault.registrationComplete();
EXPECT_NE(watchdog.get(), nullptr); EXPECT_NE(watchdog.try_get(), nullptr);
EXPECT_EQ(watchdog.get(), SingletonDirectUsage<Watchdog>::get()); EXPECT_EQ(watchdog.try_get(), SingletonDirectUsage<Watchdog>::try_get());
EXPECT_NE(watchdog.get(), named_watchdog.get()); EXPECT_NE(watchdog.try_get(), named_watchdog.try_get());
EXPECT_EQ(watchdog->livingWatchdogCount(), 2); EXPECT_EQ(watchdog.try_get()->livingWatchdogCount(), 2);
EXPECT_EQ((*watchdog).livingWatchdogCount(), 2);
vault.destroyInstances(); vault.destroyInstances();
} }
...@@ -168,22 +176,23 @@ TEST(Singleton, NamedUsage) { ...@@ -168,22 +176,23 @@ TEST(Singleton, NamedUsage) {
EXPECT_EQ(vault.registeredSingletonCount(), 3); EXPECT_EQ(vault.registeredSingletonCount(), 3);
vault.registrationComplete(); vault.registrationComplete();
{
// Verify our three singletons are distinct and non-nullptr. // Verify our three singletons are distinct and non-nullptr.
Watchdog* s1 = SingletonNamedUsage<Watchdog, Watchdog1>::get(); auto s1 = SingletonNamedUsage<Watchdog, Watchdog1>::try_get();
EXPECT_EQ(s1, watchdog1_singleton.get()); EXPECT_EQ(s1, watchdog1_singleton.try_get());
Watchdog* s2 = SingletonNamedUsage<Watchdog, Watchdog2>::get(); auto s2 = SingletonNamedUsage<Watchdog, Watchdog2>::try_get();
EXPECT_EQ(s2, watchdog2_singleton.get()); EXPECT_EQ(s2, watchdog2_singleton.try_get());
EXPECT_NE(s1, s2); EXPECT_NE(s1, s2);
Watchdog* s3 = SingletonNamedUsage<Watchdog, Watchdog3>::get(); auto s3 = SingletonNamedUsage<Watchdog, Watchdog3>::try_get();
EXPECT_EQ(s3, watchdog3_singleton.get()); EXPECT_EQ(s3, watchdog3_singleton.try_get());
EXPECT_NE(s3, s1); EXPECT_NE(s3, s1);
EXPECT_NE(s3, s2); EXPECT_NE(s3, s2);
// Verify the "default" singleton is the same as the DefaultTag-tagged // Verify the "default" singleton is the same as the DefaultTag-tagged
// singleton. // singleton.
Watchdog* s4 = SingletonNamedUsage<Watchdog>::get(); auto s4 = SingletonNamedUsage<Watchdog>::try_get();
EXPECT_EQ(s4, watchdog3_singleton.get()); EXPECT_EQ(s4, watchdog3_singleton.try_get());
}
vault.destroyInstances(); vault.destroyInstances();
} }
...@@ -203,14 +212,14 @@ TEST(Singleton, NaughtyUsage) { ...@@ -203,14 +212,14 @@ TEST(Singleton, NaughtyUsage) {
vault.registrationComplete(); vault.registrationComplete();
// Unregistered. // Unregistered.
EXPECT_DEATH(Singleton<Watchdog>::get(), ""); EXPECT_DEATH(Singleton<Watchdog>::try_get(), "");
EXPECT_DEATH(SingletonNaughtyUsage<Watchdog>::get(), ""); EXPECT_DEATH(SingletonNaughtyUsage<Watchdog>::try_get(), "");
vault.destroyInstances(); vault.destroyInstances();
auto& vault2 = *SingletonVault::singleton<NaughtyUsageTag2>(); auto& vault2 = *SingletonVault::singleton<NaughtyUsageTag2>();
EXPECT_DEATH(SingletonNaughtyUsage2<Watchdog>::get(), ""); EXPECT_DEATH(SingletonNaughtyUsage2<Watchdog>::try_get(), "");
SingletonNaughtyUsage2<Watchdog> watchdog_singleton; SingletonNaughtyUsage2<Watchdog> watchdog_singleton;
// double registration // double registration
...@@ -227,6 +236,7 @@ struct SharedPtrUsageTag {}; ...@@ -227,6 +236,7 @@ struct SharedPtrUsageTag {};
template <typename T, typename Tag = detail::DefaultTag> template <typename T, typename Tag = detail::DefaultTag>
using SingletonSharedPtrUsage = Singleton <T, Tag, SharedPtrUsageTag>; using SingletonSharedPtrUsage = Singleton <T, Tag, SharedPtrUsageTag>;
// TODO (anob): revisit this test
TEST(Singleton, SharedPtrUsage) { TEST(Singleton, SharedPtrUsage) {
struct WatchdogHolder { struct WatchdogHolder {
~WatchdogHolder() { ~WatchdogHolder() {
...@@ -256,12 +266,12 @@ TEST(Singleton, SharedPtrUsage) { ...@@ -256,12 +266,12 @@ TEST(Singleton, SharedPtrUsage) {
// Initilize holder singleton first, so that it's the last one to be // Initilize holder singleton first, so that it's the last one to be
// destroyed. // destroyed.
watchdog_holder_singleton.get(); watchdog_holder_singleton.try_get();
Watchdog* s1 = SingletonSharedPtrUsage<Watchdog>::get(); auto s1 = SingletonSharedPtrUsage<Watchdog>::try_get().get();
EXPECT_NE(s1, nullptr); EXPECT_NE(s1, nullptr);
Watchdog* s2 = SingletonSharedPtrUsage<Watchdog>::get(); auto s2 = SingletonSharedPtrUsage<Watchdog>::try_get().get();
EXPECT_NE(s2, nullptr); EXPECT_NE(s2, nullptr);
EXPECT_EQ(s1, s2); EXPECT_EQ(s1, s2);
...@@ -283,7 +293,7 @@ TEST(Singleton, SharedPtrUsage) { ...@@ -283,7 +293,7 @@ TEST(Singleton, SharedPtrUsage) {
// We should release externally locked shared_ptr, otherwise it will be // We should release externally locked shared_ptr, otherwise it will be
// considered a leak // considered a leak
watchdog_holder_singleton->watchdog = std::move(shared_s1); watchdog_holder_singleton.try_get()->watchdog = std::move(shared_s1);
LOG(ERROR) << "The following log message regarding shared_ptr is expected"; LOG(ERROR) << "The following log message regarding shared_ptr is expected";
{ {
...@@ -303,11 +313,13 @@ TEST(Singleton, SharedPtrUsage) { ...@@ -303,11 +313,13 @@ TEST(Singleton, SharedPtrUsage) {
vault.reenableInstances(); vault.reenableInstances();
{
// Singleton should be re-created only after reenableInstances() was called. // Singleton should be re-created only after reenableInstances() was called.
Watchdog* new_s1 = SingletonSharedPtrUsage<Watchdog>::get(); auto new_s1 = SingletonSharedPtrUsage<Watchdog>::try_get();
// Track serial number rather than pointer since the memory could be // Track serial number rather than pointer since the memory could be
// re-used when we create new_s1. // re-used when we create new_s1.
EXPECT_NE(new_s1->serial_number, old_serial); EXPECT_NE(new_s1->serial_number, old_serial);
}
auto new_s1_weak = SingletonSharedPtrUsage<Watchdog>::get_weak(); auto new_s1_weak = SingletonSharedPtrUsage<Watchdog>::get_weak();
auto new_s1_shared = new_s1_weak.lock(); auto new_s1_shared = new_s1_weak.lock();
...@@ -337,7 +349,7 @@ using SingletonNeedy = Singleton <T, Tag, NeedyTag>; ...@@ -337,7 +349,7 @@ using SingletonNeedy = Singleton <T, Tag, NeedyTag>;
struct NeededSingleton {}; struct NeededSingleton {};
struct NeedySingleton { struct NeedySingleton {
NeedySingleton() { NeedySingleton() {
auto unused = SingletonNeedy<NeededSingleton>::get(); auto unused = SingletonNeedy<NeededSingleton>::try_get();
EXPECT_NE(unused, nullptr); EXPECT_NE(unused, nullptr);
} }
}; };
...@@ -349,7 +361,7 @@ using SingletonSelfNeedy = Singleton <T, Tag, SelfNeedyTag>; ...@@ -349,7 +361,7 @@ using SingletonSelfNeedy = Singleton <T, Tag, SelfNeedyTag>;
struct SelfNeedySingleton { struct SelfNeedySingleton {
SelfNeedySingleton() { SelfNeedySingleton() {
auto unused = SingletonSelfNeedy<SelfNeedySingleton>::get(); auto unused = SingletonSelfNeedy<SelfNeedySingleton>::try_get();
EXPECT_NE(unused, nullptr); EXPECT_NE(unused, nullptr);
} }
}; };
...@@ -364,14 +376,15 @@ TEST(Singleton, SingletonDependencies) { ...@@ -364,14 +376,15 @@ TEST(Singleton, SingletonDependencies) {
EXPECT_EQ(needy_vault.registeredSingletonCount(), 2); EXPECT_EQ(needy_vault.registeredSingletonCount(), 2);
EXPECT_EQ(needy_vault.livingSingletonCount(), 0); EXPECT_EQ(needy_vault.livingSingletonCount(), 0);
auto needy = SingletonNeedy<NeedySingleton>::get(); auto needy = SingletonNeedy<NeedySingleton>::try_get();
EXPECT_EQ(needy_vault.livingSingletonCount(), 2); EXPECT_EQ(needy_vault.livingSingletonCount(), 2);
SingletonSelfNeedy<SelfNeedySingleton> self_needy_singleton; SingletonSelfNeedy<SelfNeedySingleton> self_needy_singleton;
auto& self_needy_vault = *SingletonVault::singleton<SelfNeedyTag>(); auto& self_needy_vault = *SingletonVault::singleton<SelfNeedyTag>();
self_needy_vault.registrationComplete(); self_needy_vault.registrationComplete();
EXPECT_DEATH([]() { SingletonSelfNeedy<SelfNeedySingleton>::get(); }(), ""); EXPECT_DEATH([]() { SingletonSelfNeedy<SelfNeedySingleton>::try_get(); }(),
"");
} }
// A test to ensure multiple threads contending on singleton creation // A test to ensure multiple threads contending on singleton creation
...@@ -396,7 +409,7 @@ TEST(Singleton, SingletonConcurrency) { ...@@ -396,7 +409,7 @@ TEST(Singleton, SingletonConcurrency) {
auto func = [&gatekeeper]() { auto func = [&gatekeeper]() {
gatekeeper.lock(); gatekeeper.lock();
gatekeeper.unlock(); gatekeeper.unlock();
auto unused = SingletonConcurrency<Slowpoke>::get(); auto unused = SingletonConcurrency<Slowpoke>::try_get();
}; };
EXPECT_EQ(vault.livingSingletonCount(), 0); EXPECT_EQ(vault.livingSingletonCount(), 0);
...@@ -498,13 +511,13 @@ TEST(Singleton, MockTest) { ...@@ -498,13 +511,13 @@ TEST(Singleton, MockTest) {
// Registring singletons after registrationComplete called works // Registring singletons after registrationComplete called works
// with make_mock (but not with Singleton ctor). // with make_mock (but not with Singleton ctor).
EXPECT_EQ(vault.registeredSingletonCount(), 1); EXPECT_EQ(vault.registeredSingletonCount(), 1);
int serial_count_first = SingletonMock<Watchdog>::get()->serial_number; int serial_count_first = SingletonMock<Watchdog>::try_get()->serial_number;
// Override existing mock using make_mock. // Override existing mock using make_mock.
SingletonMock<Watchdog>::make_mock(); SingletonMock<Watchdog>::make_mock();
EXPECT_EQ(vault.registeredSingletonCount(), 1); EXPECT_EQ(vault.registeredSingletonCount(), 1);
int serial_count_mock = SingletonMock<Watchdog>::get()->serial_number; int serial_count_mock = SingletonMock<Watchdog>::try_get()->serial_number;
// If serial_count value is the same, then singleton was not replaced. // If serial_count value is the same, then singleton was not replaced.
EXPECT_NE(serial_count_first, serial_count_mock); EXPECT_NE(serial_count_first, serial_count_mock);
...@@ -538,7 +551,7 @@ SingletonBenchmark<BenchmarkSingleton, GetWeakTag> benchmark_singleton_get_weak; ...@@ -538,7 +551,7 @@ SingletonBenchmark<BenchmarkSingleton, GetWeakTag> benchmark_singleton_get_weak;
BENCHMARK_RELATIVE(FollySingleton, n) { BENCHMARK_RELATIVE(FollySingleton, n) {
for (size_t i = 0; i < n; ++i) { for (size_t i = 0; i < n; ++i) {
doNotOptimizeAway(SingletonBenchmark<BenchmarkSingleton, GetTag>::get()); SingletonBenchmark<BenchmarkSingleton, GetTag>::try_get();
} }
} }
......
...@@ -48,7 +48,7 @@ TEST(SingletonVault, singletonsAreCreatedAndDestroyed) { ...@@ -48,7 +48,7 @@ TEST(SingletonVault, singletonsAreCreatedAndDestroyed) {
auto vault = folly::SingletonVault::singleton<TestTag>(); auto vault = folly::SingletonVault::singleton<TestTag>();
SingletonTest<InstanceCounter> counter_singleton; SingletonTest<InstanceCounter> counter_singleton;
SingletonVault_registrationComplete((SingletonVault_t*) vault); SingletonVault_registrationComplete((SingletonVault_t*) vault);
InstanceCounter *counter = SingletonTest<InstanceCounter>::get(); SingletonTest<InstanceCounter>::try_get();
EXPECT_EQ(instance_counter_instances, 1); EXPECT_EQ(instance_counter_instances, 1);
SingletonVault_destroyInstances((SingletonVault_t*) vault); SingletonVault_destroyInstances((SingletonVault_t*) vault);
EXPECT_EQ(instance_counter_instances, 0); EXPECT_EQ(instance_counter_instances, 0);
......
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