Commit 34a5bd8c authored by Rui Zhang's avatar Rui Zhang Committed by Facebook Github Bot

Add Singleton::apply and unit tests.

Summary: Adds a callback interface to facilitate adding elision support for folly::Singleton. Planned to add context-aware elision support which will use an adaptation method based on the interface's callsite information (ie, the type of the Singleton instance, T, and the type of the callback function, F).

Reviewed By: nbronson

Differential Revision: D15970597

fbshipit-source-id: 1010322c64d1b7900b4bef5b35059238efabb071
parent 10e490c1
...@@ -142,6 +142,12 @@ folly::ReadMostlySharedPtr<T> SingletonHolder<T>::try_get_fast() { ...@@ -142,6 +142,12 @@ folly::ReadMostlySharedPtr<T> SingletonHolder<T>::try_get_fast() {
return instance_weak_fast_.lock(); return instance_weak_fast_.lock();
} }
template <typename T>
template <typename Func>
invoke_result_t<Func, T*> detail::SingletonHolder<T>::apply(Func f) {
return f(try_get().get());
}
template <typename T> template <typename T>
void SingletonHolder<T>::vivify() { void SingletonHolder<T>::vivify() {
if (UNLIKELY( if (UNLIKELY(
......
...@@ -313,6 +313,8 @@ struct SingletonHolder : public SingletonHolderBase { ...@@ -313,6 +313,8 @@ struct SingletonHolder : public SingletonHolderBase {
inline std::weak_ptr<T> get_weak(); inline std::weak_ptr<T> get_weak();
inline std::shared_ptr<T> try_get(); inline std::shared_ptr<T> try_get();
inline folly::ReadMostlySharedPtr<T> try_get_fast(); inline folly::ReadMostlySharedPtr<T> try_get_fast();
template <typename Func>
inline invoke_result_t<Func, T*> apply(Func f);
inline void vivify(); inline void vivify();
void registerSingleton(CreateFunc c, TeardownFunc t); void registerSingleton(CreateFunc c, TeardownFunc t);
...@@ -599,6 +601,22 @@ class Singleton { ...@@ -599,6 +601,22 @@ class Singleton {
return getEntry().try_get_fast(); return getEntry().try_get_fast();
} }
/**
* Applies a callback to the possibly-nullptr singleton instance, returning
* the callback's result. That is, the following two are functionally
* equivalent:
* singleton.apply(std::ref(f));
* f(singleton.try_get().get());
*
* For example, the following returns the singleton
* instance directly without any extra operations on the instance:
* auto ret = Singleton<T>::apply([](auto* v) { return v; });
*/
template <typename Func>
static invoke_result_t<Func, T*> apply(Func f) {
return getEntry().apply(std::ref(f));
}
// Quickly ensure the instance exists. // Quickly ensure the instance exists.
static void vivify() { static void vivify() {
getEntry().vivify(); getEntry().vivify();
......
...@@ -37,6 +37,11 @@ using namespace folly; ...@@ -37,6 +37,11 @@ using namespace folly;
TEST(Singleton, MissingSingleton) { TEST(Singleton, MissingSingleton) {
EXPECT_DEATH( EXPECT_DEATH(
[]() { auto u = Singleton<UnregisteredWatchdog>::try_get(); }(), ""); []() { auto u = Singleton<UnregisteredWatchdog>::try_get(); }(), "");
EXPECT_DEATH(
[]() {
Singleton<UnregisteredWatchdog>::apply([](auto* v) { return v; });
}(),
"");
} }
struct BasicUsageTag {}; struct BasicUsageTag {};
...@@ -63,17 +68,34 @@ TEST(Singleton, BasicUsage) { ...@@ -63,17 +68,34 @@ TEST(Singleton, BasicUsage) {
std::shared_ptr<Watchdog> s1 = SingletonBasicUsage<Watchdog>::try_get(); std::shared_ptr<Watchdog> s1 = SingletonBasicUsage<Watchdog>::try_get();
EXPECT_NE(s1, nullptr); EXPECT_NE(s1, nullptr);
SingletonBasicUsage<Watchdog>::apply([](auto*) {});
SUCCEED();
auto w1 = SingletonBasicUsage<Watchdog>::apply([](auto* v) { return v; });
EXPECT_NE(w1, nullptr);
std::shared_ptr<Watchdog> s2 = SingletonBasicUsage<Watchdog>::try_get(); std::shared_ptr<Watchdog> s2 = SingletonBasicUsage<Watchdog>::try_get();
EXPECT_NE(s2, nullptr); EXPECT_NE(s2, nullptr);
auto w2 = SingletonBasicUsage<Watchdog>::apply([](auto* v) { return v; });
EXPECT_NE(w2, nullptr);
EXPECT_EQ(s1, s2); EXPECT_EQ(s1, s2);
EXPECT_EQ(w1, w2);
EXPECT_EQ(s1.get(), SingletonBasicUsage<Watchdog>::try_get_fast().get()); EXPECT_EQ(s1.get(), SingletonBasicUsage<Watchdog>::try_get_fast().get());
EXPECT_EQ(w1, SingletonBasicUsage<Watchdog>::try_get_fast().get());
std::shared_ptr<ChildWatchdog> s3 = std::shared_ptr<ChildWatchdog> s3 =
SingletonBasicUsage<ChildWatchdog>::try_get(); SingletonBasicUsage<ChildWatchdog>::try_get();
EXPECT_NE(s3, nullptr); EXPECT_NE(s3, nullptr);
EXPECT_NE(s2, s3); EXPECT_NE(s2, s3);
SingletonBasicUsage<ChildWatchdog>::apply([](auto*) {});
SUCCEED();
auto w3 =
SingletonBasicUsage<ChildWatchdog>::apply([](auto* v) { return v; });
EXPECT_NE(w3, nullptr);
EXPECT_NE(w2, w3);
EXPECT_EQ(vault.registeredSingletonCount(), 2); EXPECT_EQ(vault.registeredSingletonCount(), 2);
EXPECT_EQ(vault.livingSingletonCount(), 2); EXPECT_EQ(vault.livingSingletonCount(), 2);
} }
...@@ -105,6 +127,22 @@ TEST(Singleton, DirectUsage) { ...@@ -105,6 +127,22 @@ TEST(Singleton, DirectUsage) {
EXPECT_NE(watchdog.try_get(), named_watchdog.try_get()); EXPECT_NE(watchdog.try_get(), named_watchdog.try_get());
EXPECT_EQ(watchdog.try_get()->livingWatchdogCount(), 2); EXPECT_EQ(watchdog.try_get()->livingWatchdogCount(), 2);
watchdog.apply([](auto*) {});
SingletonDirectUsage<Watchdog>::apply([](auto*) {});
named_watchdog.apply([](auto*) {});
SUCCEED();
// Cannot use the Singleton::apply directly in the EXPECT_* macros due to
// "error: lambda expression in an unevaluated operand"
auto w1 = watchdog.apply([](auto* v) { return v; });
auto w2 = SingletonDirectUsage<Watchdog>::apply([](auto* v) { return v; });
auto w3 = named_watchdog.apply([](auto* v) { return v; });
EXPECT_NE(w1, nullptr);
EXPECT_EQ(w1, w2);
EXPECT_NE(w1, w3);
auto count = watchdog.apply([](auto* v) { return v->livingWatchdogCount(); });
EXPECT_EQ(count, 2);
vault.destroyInstances(); vault.destroyInstances();
} }
...@@ -141,10 +179,29 @@ TEST(Singleton, NamedUsage) { ...@@ -141,10 +179,29 @@ TEST(Singleton, NamedUsage) {
EXPECT_NE(s3, s1); EXPECT_NE(s3, s1);
EXPECT_NE(s3, s2); EXPECT_NE(s3, s2);
auto w1 = SingletonNamedUsage<Watchdog, Watchdog1>::apply(
[](auto* v) { return v; });
auto wd1 = watchdog1_singleton.apply([](auto* v) { return v; });
EXPECT_EQ(w1, wd1);
auto w2 = SingletonNamedUsage<Watchdog, Watchdog2>::apply(
[](auto* v) { return v; });
auto wd2 = watchdog2_singleton.apply([](auto* v) { return v; });
EXPECT_EQ(w2, wd2);
EXPECT_NE(w1, w2);
auto w3 = SingletonNamedUsage<Watchdog, Watchdog3>::apply(
[](auto* v) { return v; });
auto wd3 = watchdog3_singleton.apply([](auto* v) { return v; });
EXPECT_EQ(w3, wd3);
EXPECT_NE(w3, w1);
EXPECT_NE(w3, w2);
// Verify the "default" singleton is the same as the DefaultTag-tagged // Verify the "default" singleton is the same as the DefaultTag-tagged
// singleton. // singleton.
auto s4 = SingletonNamedUsage<Watchdog>::try_get(); auto s4 = SingletonNamedUsage<Watchdog>::try_get();
EXPECT_EQ(s4, watchdog3_singleton.try_get()); EXPECT_EQ(s4, watchdog3_singleton.try_get());
auto w4 = SingletonNamedUsage<Watchdog>::apply([](auto* v) { return v; });
EXPECT_EQ(w4, wd3);
} }
vault.destroyInstances(); vault.destroyInstances();
...@@ -166,13 +223,18 @@ TEST(Singleton, NaughtyUsage) { ...@@ -166,13 +223,18 @@ TEST(Singleton, NaughtyUsage) {
// Unregistered. // Unregistered.
EXPECT_DEATH(Singleton<Watchdog>::try_get(), ""); EXPECT_DEATH(Singleton<Watchdog>::try_get(), "");
EXPECT_DEATH(Singleton<Watchdog>::apply([](auto* v) { return v; }), "");
EXPECT_DEATH(SingletonNaughtyUsage<Watchdog>::try_get(), ""); EXPECT_DEATH(SingletonNaughtyUsage<Watchdog>::try_get(), "");
EXPECT_DEATH(
SingletonNaughtyUsage<Watchdog>::apply([](auto* v) { return v; }), "");
vault.destroyInstances(); vault.destroyInstances();
auto& vault2 = *SingletonVault::singleton<NaughtyUsageTag2>(); auto& vault2 = *SingletonVault::singleton<NaughtyUsageTag2>();
EXPECT_DEATH(SingletonNaughtyUsage2<Watchdog>::try_get(), ""); EXPECT_DEATH(SingletonNaughtyUsage2<Watchdog>::try_get(), "");
EXPECT_DEATH(
SingletonNaughtyUsage2<Watchdog>::apply([](auto* v) { return v; }), "");
SingletonNaughtyUsage2<Watchdog> watchdog_singleton; SingletonNaughtyUsage2<Watchdog> watchdog_singleton;
// double registration // double registration
...@@ -229,15 +291,24 @@ TEST(Singleton, SharedPtrUsage) { ...@@ -229,15 +291,24 @@ 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.try_get(); watchdog_holder_singleton.try_get();
watchdog_holder_singleton.apply([](auto* v) { return v; });
auto s1 = SingletonSharedPtrUsage<Watchdog>::try_get().get(); auto s1 = SingletonSharedPtrUsage<Watchdog>::try_get().get();
EXPECT_NE(s1, nullptr); EXPECT_NE(s1, nullptr);
auto w1 = SingletonSharedPtrUsage<Watchdog>::apply([](auto* v) { return v; });
EXPECT_NE(w1, nullptr);
auto s2 = SingletonSharedPtrUsage<Watchdog>::try_get().get(); auto s2 = SingletonSharedPtrUsage<Watchdog>::try_get().get();
EXPECT_NE(s2, nullptr); EXPECT_NE(s2, nullptr);
auto w2 = SingletonSharedPtrUsage<Watchdog>::apply([](auto* v) { return v; });
EXPECT_NE(w2, nullptr);
EXPECT_EQ(s1, s2); EXPECT_EQ(s1, s2);
EXPECT_EQ(w1, w2);
auto weak_s1 = SingletonSharedPtrUsage<Watchdog>::get_weak(); auto weak_s1 = SingletonSharedPtrUsage<Watchdog>::get_weak();
auto shared_s1 = weak_s1.lock(); auto shared_s1 = weak_s1.lock();
...@@ -254,7 +325,9 @@ TEST(Singleton, SharedPtrUsage) { ...@@ -254,7 +325,9 @@ 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.try_get()->watchdog = std::move(shared_s1); watchdog_holder_singleton.try_get()->watchdog = shared_s1;
watchdog_holder_singleton.apply(
[&](auto* v) { v->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";
{ {
...@@ -278,9 +351,12 @@ TEST(Singleton, SharedPtrUsage) { ...@@ -278,9 +351,12 @@ TEST(Singleton, SharedPtrUsage) {
{ {
// Singleton should be re-created only after reenableInstances() was called. // Singleton should be re-created only after reenableInstances() was called.
auto new_s1 = SingletonSharedPtrUsage<Watchdog>::try_get(); auto new_s1 = SingletonSharedPtrUsage<Watchdog>::try_get();
auto new_w1 =
SingletonSharedPtrUsage<Watchdog>::apply([](auto* v) { return v; });
// 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);
EXPECT_NE(new_w1->serial_number, old_serial);
} }
auto new_s1_weak = SingletonSharedPtrUsage<Watchdog>::get_weak(); auto new_s1_weak = SingletonSharedPtrUsage<Watchdog>::get_weak();
...@@ -312,8 +388,12 @@ using SingletonNeedy = Singleton<T, Tag, NeedyTag>; ...@@ -312,8 +388,12 @@ using SingletonNeedy = Singleton<T, Tag, NeedyTag>;
struct NeededSingleton {}; struct NeededSingleton {};
struct NeedySingleton { struct NeedySingleton {
NeedySingleton() { NeedySingleton() {
auto unused = SingletonNeedy<NeededSingleton>::try_get(); auto unused1 = SingletonNeedy<NeededSingleton>::try_get();
EXPECT_NE(unused, nullptr); EXPECT_NE(unused1, nullptr);
auto unused2 =
SingletonNeedy<NeededSingleton>::apply([](auto* v) { return v; });
EXPECT_NE(unused2, nullptr);
} }
}; };
...@@ -324,8 +404,12 @@ using SingletonSelfNeedy = Singleton<T, Tag, SelfNeedyTag>; ...@@ -324,8 +404,12 @@ using SingletonSelfNeedy = Singleton<T, Tag, SelfNeedyTag>;
struct SelfNeedySingleton { struct SelfNeedySingleton {
SelfNeedySingleton() { SelfNeedySingleton() {
auto unused = SingletonSelfNeedy<SelfNeedySingleton>::try_get(); auto unused1 = SingletonSelfNeedy<SelfNeedySingleton>::try_get();
EXPECT_NE(unused, nullptr); EXPECT_NE(unused1, nullptr);
auto unused2 =
SingletonSelfNeedy<SelfNeedySingleton>::apply([](auto v) { return v; });
EXPECT_NE(unused2, nullptr);
} }
}; };
...@@ -342,12 +426,23 @@ TEST(Singleton, SingletonDependencies) { ...@@ -342,12 +426,23 @@ TEST(Singleton, SingletonDependencies) {
auto needy = SingletonNeedy<NeedySingleton>::try_get(); auto needy = SingletonNeedy<NeedySingleton>::try_get();
EXPECT_EQ(needy_vault.livingSingletonCount(), 2); EXPECT_EQ(needy_vault.livingSingletonCount(), 2);
auto another_needy =
SingletonNeedy<NeedySingleton>::apply([](auto* v) { return v; });
(void)another_needy;
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( EXPECT_DEATH(
[]() { SingletonSelfNeedy<SelfNeedySingleton>::try_get(); }(), ""); []() { SingletonSelfNeedy<SelfNeedySingleton>::try_get(); }(), "");
EXPECT_DEATH(
[]() {
SingletonSelfNeedy<SelfNeedySingleton>::apply(
[](auto* v) { return v; });
}(),
"");
} }
// A test to ensure multiple threads contending on singleton creation // A test to ensure multiple threads contending on singleton creation
...@@ -374,7 +469,10 @@ TEST(Singleton, SingletonConcurrency) { ...@@ -374,7 +469,10 @@ TEST(Singleton, SingletonConcurrency) {
auto func = [&gatekeeper]() { auto func = [&gatekeeper]() {
gatekeeper.lock(); gatekeeper.lock();
gatekeeper.unlock(); gatekeeper.unlock();
auto unused = SingletonConcurrency<Slowpoke>::try_get(); auto unused1 = SingletonConcurrency<Slowpoke>::try_get();
auto unused2 =
SingletonConcurrency<Slowpoke>::apply([](auto* v) { return v; });
EXPECT_EQ(unused1.get(), unused2);
}; };
EXPECT_EQ(vault.livingSingletonCount(), 0); EXPECT_EQ(vault.livingSingletonCount(), 0);
...@@ -414,7 +512,7 @@ TEST(Singleton, SingletonCreationError) { ...@@ -414,7 +512,7 @@ TEST(Singleton, SingletonCreationError) {
EXPECT_THROW(error_once_singleton.try_get(), std::runtime_error); EXPECT_THROW(error_once_singleton.try_get(), std::runtime_error);
// second time it'll work fine // second time it'll work fine
error_once_singleton.try_get(); error_once_singleton.apply([](auto* v) { return v; });
SUCCEED(); SUCCEED();
} }
...@@ -429,7 +527,13 @@ TEST(Singleton, SingletonConcurrencyStress) { ...@@ -429,7 +527,13 @@ TEST(Singleton, SingletonConcurrencyStress) {
std::vector<std::thread> ts; std::vector<std::thread> ts;
for (size_t i = 0; i < 100; ++i) { for (size_t i = 0; i < 100; ++i) {
ts.emplace_back([&]() { slowpoke_singleton.try_get(); }); ts.emplace_back([i, &slowpoke_singleton]() {
if ((i % 2) == 0) {
slowpoke_singleton.try_get();
} else {
slowpoke_singleton.apply([](auto) {});
}
});
} }
for (size_t i = 0; i < 100; ++i) { for (size_t i = 0; i < 100; ++i) {
...@@ -618,6 +722,42 @@ TEST(Singleton, MockTest) { ...@@ -618,6 +722,42 @@ TEST(Singleton, MockTest) {
vault.destroyInstances(); vault.destroyInstances();
} }
TEST(Singleton, MockTestWithApply) {
auto& vault = *SingletonVault::singleton<MockTag>();
SingletonMock<Watchdog> 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 =
SingletonMock<Watchdog>::apply([](auto* v) { return v->serial_number; });
// Override existing mock using make_mock.
SingletonMock<Watchdog>::make_mock();
EXPECT_EQ(vault.registeredSingletonCount(), 1);
int serial_count_mock =
SingletonMock<Watchdog>::apply([](auto* v) { return v->serial_number; });
// If serial_count value is the same, then singleton was not replaced.
EXPECT_NE(serial_count_first, serial_count_mock);
// Override existing mock using make_mock one more time
SingletonMock<Watchdog>::make_mock();
EXPECT_EQ(vault.registeredSingletonCount(), 1);
int serial_count_mock2 =
SingletonMock<Watchdog>::apply([](auto* v) { return v->serial_number; });
// If serial_count value is the same, then singleton was not replaced.
EXPECT_NE(serial_count_first, serial_count_mock2);
EXPECT_NE(serial_count_mock, serial_count_mock2);
vault.destroyInstances();
}
#ifndef _MSC_VER #ifndef _MSC_VER
// Subprocess isn't currently supported under MSVC. // Subprocess isn't currently supported under MSVC.
TEST(Singleton, DoubleRegistrationLogging) { TEST(Singleton, DoubleRegistrationLogging) {
...@@ -660,6 +800,16 @@ TEST(Singleton, CustomCreator) { ...@@ -660,6 +800,16 @@ TEST(Singleton, CustomCreator) {
EXPECT_EQ(std::string("foo"), x2p->a2); EXPECT_EQ(std::string("foo"), x2p->a2);
} }
TEST(Singleton, CustomCreatorViaApply) {
X x1;
X* x3p = singleton_x.apply([](auto* v) { return v; });
EXPECT_NE(nullptr, x3p);
EXPECT_NE(x1.a1, x3p->a1);
EXPECT_NE(x1.a2, x3p->a2);
EXPECT_EQ(42, x3p->a1);
EXPECT_EQ(std::string("foo"), x3p->a2);
}
struct ConcurrentCreationDestructionTag {}; struct ConcurrentCreationDestructionTag {};
template <typename T, typename Tag = detail::DefaultTag> template <typename T, typename Tag = detail::DefaultTag>
using SingletonConcurrentCreationDestruction = using SingletonConcurrentCreationDestruction =
...@@ -672,9 +822,14 @@ struct SlowpokeNeedySingleton { ...@@ -672,9 +822,14 @@ struct SlowpokeNeedySingleton {
slowpokeNeedySingletonBaton.post(); slowpokeNeedySingletonBaton.post();
/* sleep override */ std::this_thread::sleep_for( /* sleep override */ std::this_thread::sleep_for(
std::chrono::milliseconds(100)); std::chrono::milliseconds(100));
auto unused = auto unused1 =
SingletonConcurrentCreationDestruction<NeededSingleton>::try_get(); SingletonConcurrentCreationDestruction<NeededSingleton>::try_get();
EXPECT_NE(unused, nullptr); EXPECT_NE(unused1, nullptr);
auto unused2 =
SingletonConcurrentCreationDestruction<NeededSingleton>::apply(
[](auto* v) { return v; });
EXPECT_NE(unused2, nullptr);
} }
}; };
...@@ -684,7 +839,8 @@ TEST(Singleton, ConcurrentCreationDestruction) { ...@@ -684,7 +839,8 @@ TEST(Singleton, ConcurrentCreationDestruction) {
SingletonConcurrentCreationDestruction<SlowpokeNeedySingleton> needySingleton; SingletonConcurrentCreationDestruction<SlowpokeNeedySingleton> needySingleton;
vault.registrationComplete(); vault.registrationComplete();
std::thread needyThread([&] { needySingleton.try_get(); }); std::thread needyThread(
[&] { needySingleton.apply([](auto* v) { return v; }); });
slowpokeNeedySingletonBaton.wait(); slowpokeNeedySingletonBaton.wait();
...@@ -736,6 +892,29 @@ TEST(Singleton, MainThreadDestructor) { ...@@ -736,6 +892,29 @@ TEST(Singleton, MainThreadDestructor) {
t.join(); t.join();
} }
TEST(Singleton, MainThreadDestructorWithApply) {
auto& vault = *SingletonVault::singleton<MainThreadDestructorTag>();
SingletonMainThreadDestructor<ThreadLoggingSingleton> singleton;
vault.registrationComplete();
EXPECT_EQ(std::thread::id(), ThreadLoggingSingleton::initThread);
singleton.apply([](auto* v) { return v; });
EXPECT_EQ(std::this_thread::get_id(), ThreadLoggingSingleton::initThread);
std::thread t([instance = singleton.apply([](auto* v) { return v; })] {
/* sleep override */ std::this_thread::sleep_for(
std::chrono::milliseconds{100});
});
EXPECT_EQ(std::thread::id(), ThreadLoggingSingleton::destroyThread);
vault.destroyInstances();
EXPECT_EQ(std::this_thread::get_id(), ThreadLoggingSingleton::destroyThread);
t.join();
}
TEST(Singleton, DoubleMakeMockAfterTryGet) { TEST(Singleton, DoubleMakeMockAfterTryGet) {
// to keep track of calls to ctor and dtor below // to keep track of calls to ctor and dtor below
struct Counts { struct Counts {
...@@ -784,6 +963,54 @@ TEST(Singleton, DoubleMakeMockAfterTryGet) { ...@@ -784,6 +963,54 @@ TEST(Singleton, DoubleMakeMockAfterTryGet) {
EXPECT_EQ(1, counts.dtor); EXPECT_EQ(1, counts.dtor);
} }
TEST(Singleton, DoubleMakeMockAfterTryGetWithApply) {
// to keep track of calls to ctor and dtor below
struct Counts {
size_t ctor = 0;
size_t dtor = 0;
};
// a test type which keeps track of its ctor and dtor calls
struct VaultTag {};
struct PrivateTag {};
struct Object {
explicit Object(Counts& counts) : counts_(counts) {
++counts_.ctor;
}
~Object() {
++counts_.dtor;
}
Counts& counts_;
};
using SingletonObject = Singleton<Object, PrivateTag, VaultTag>;
// register everything
Counts counts;
auto& vault = *SingletonVault::singleton<VaultTag>();
auto new_object = [&] { return new Object(counts); };
SingletonObject object_(new_object);
vault.registrationComplete();
// no eager inits, nada (sanity)
EXPECT_EQ(0, counts.ctor);
EXPECT_EQ(0, counts.dtor);
// explicit request, ctor
SingletonObject::apply([](auto* v) { return v; });
EXPECT_EQ(1, counts.ctor);
EXPECT_EQ(0, counts.dtor);
// first make_mock, dtor (ctor is lazy)
SingletonObject::make_mock(new_object);
EXPECT_EQ(1, counts.ctor);
EXPECT_EQ(1, counts.dtor);
// second make_mock, nada (dtor already ran, ctor is lazy)
SingletonObject::make_mock(new_object);
EXPECT_EQ(1, counts.ctor);
EXPECT_EQ(1, counts.dtor);
}
TEST(Singleton, LeakySingletonLSAN) { TEST(Singleton, LeakySingletonLSAN) {
struct PrivateTag {}; struct PrivateTag {};
static folly::LeakySingleton<int, PrivateTag> gPtr; static folly::LeakySingleton<int, PrivateTag> gPtr;
......
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