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() {
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>
void SingletonHolder<T>::vivify() {
if (UNLIKELY(
......
......@@ -313,6 +313,8 @@ struct SingletonHolder : public SingletonHolderBase {
inline std::weak_ptr<T> get_weak();
inline std::shared_ptr<T> try_get();
inline folly::ReadMostlySharedPtr<T> try_get_fast();
template <typename Func>
inline invoke_result_t<Func, T*> apply(Func f);
inline void vivify();
void registerSingleton(CreateFunc c, TeardownFunc t);
......@@ -599,6 +601,22 @@ class Singleton {
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.
static void vivify() {
getEntry().vivify();
......
......@@ -37,6 +37,11 @@ using namespace folly;
TEST(Singleton, MissingSingleton) {
EXPECT_DEATH(
[]() { auto u = Singleton<UnregisteredWatchdog>::try_get(); }(), "");
EXPECT_DEATH(
[]() {
Singleton<UnregisteredWatchdog>::apply([](auto* v) { return v; });
}(),
"");
}
struct BasicUsageTag {};
......@@ -63,17 +68,34 @@ TEST(Singleton, BasicUsage) {
std::shared_ptr<Watchdog> s1 = SingletonBasicUsage<Watchdog>::try_get();
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();
EXPECT_NE(s2, nullptr);
auto w2 = SingletonBasicUsage<Watchdog>::apply([](auto* v) { return v; });
EXPECT_NE(w2, nullptr);
EXPECT_EQ(s1, s2);
EXPECT_EQ(w1, w2);
EXPECT_EQ(s1.get(), SingletonBasicUsage<Watchdog>::try_get_fast().get());
EXPECT_EQ(w1, SingletonBasicUsage<Watchdog>::try_get_fast().get());
std::shared_ptr<ChildWatchdog> s3 =
SingletonBasicUsage<ChildWatchdog>::try_get();
EXPECT_NE(s3, nullptr);
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.livingSingletonCount(), 2);
}
......@@ -105,6 +127,22 @@ TEST(Singleton, DirectUsage) {
EXPECT_NE(watchdog.try_get(), named_watchdog.try_get());
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();
}
......@@ -141,10 +179,29 @@ TEST(Singleton, NamedUsage) {
EXPECT_NE(s3, s1);
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
// singleton.
auto s4 = SingletonNamedUsage<Watchdog>::try_get();
EXPECT_EQ(s4, watchdog3_singleton.try_get());
auto w4 = SingletonNamedUsage<Watchdog>::apply([](auto* v) { return v; });
EXPECT_EQ(w4, wd3);
}
vault.destroyInstances();
......@@ -166,13 +223,18 @@ TEST(Singleton, NaughtyUsage) {
// Unregistered.
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>::apply([](auto* v) { return v; }), "");
vault.destroyInstances();
auto& vault2 = *SingletonVault::singleton<NaughtyUsageTag2>();
EXPECT_DEATH(SingletonNaughtyUsage2<Watchdog>::try_get(), "");
EXPECT_DEATH(
SingletonNaughtyUsage2<Watchdog>::apply([](auto* v) { return v; }), "");
SingletonNaughtyUsage2<Watchdog> watchdog_singleton;
// double registration
......@@ -229,15 +291,24 @@ TEST(Singleton, SharedPtrUsage) {
// Initilize holder singleton first, so that it's the last one to be
// destroyed.
watchdog_holder_singleton.try_get();
watchdog_holder_singleton.apply([](auto* v) { return v; });
auto s1 = SingletonSharedPtrUsage<Watchdog>::try_get().get();
EXPECT_NE(s1, nullptr);
auto w1 = SingletonSharedPtrUsage<Watchdog>::apply([](auto* v) { return v; });
EXPECT_NE(w1, nullptr);
auto s2 = SingletonSharedPtrUsage<Watchdog>::try_get().get();
EXPECT_NE(s2, nullptr);
auto w2 = SingletonSharedPtrUsage<Watchdog>::apply([](auto* v) { return v; });
EXPECT_NE(w2, nullptr);
EXPECT_EQ(s1, s2);
EXPECT_EQ(w1, w2);
auto weak_s1 = SingletonSharedPtrUsage<Watchdog>::get_weak();
auto shared_s1 = weak_s1.lock();
......@@ -254,7 +325,9 @@ TEST(Singleton, SharedPtrUsage) {
// We should release externally locked shared_ptr, otherwise it will be
// 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";
{
......@@ -278,9 +351,12 @@ TEST(Singleton, SharedPtrUsage) {
{
// Singleton should be re-created only after reenableInstances() was called.
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
// re-used when we create new_s1.
EXPECT_NE(new_s1->serial_number, old_serial);
EXPECT_NE(new_w1->serial_number, old_serial);
}
auto new_s1_weak = SingletonSharedPtrUsage<Watchdog>::get_weak();
......@@ -312,8 +388,12 @@ using SingletonNeedy = Singleton<T, Tag, NeedyTag>;
struct NeededSingleton {};
struct NeedySingleton {
NeedySingleton() {
auto unused = SingletonNeedy<NeededSingleton>::try_get();
EXPECT_NE(unused, nullptr);
auto unused1 = SingletonNeedy<NeededSingleton>::try_get();
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>;
struct SelfNeedySingleton {
SelfNeedySingleton() {
auto unused = SingletonSelfNeedy<SelfNeedySingleton>::try_get();
EXPECT_NE(unused, nullptr);
auto unused1 = SingletonSelfNeedy<SelfNeedySingleton>::try_get();
EXPECT_NE(unused1, nullptr);
auto unused2 =
SingletonSelfNeedy<SelfNeedySingleton>::apply([](auto v) { return v; });
EXPECT_NE(unused2, nullptr);
}
};
......@@ -342,12 +426,23 @@ TEST(Singleton, SingletonDependencies) {
auto needy = SingletonNeedy<NeedySingleton>::try_get();
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;
auto& self_needy_vault = *SingletonVault::singleton<SelfNeedyTag>();
self_needy_vault.registrationComplete();
EXPECT_DEATH(
[]() { SingletonSelfNeedy<SelfNeedySingleton>::try_get(); }(), "");
EXPECT_DEATH(
[]() {
SingletonSelfNeedy<SelfNeedySingleton>::apply(
[](auto* v) { return v; });
}(),
"");
}
// A test to ensure multiple threads contending on singleton creation
......@@ -374,7 +469,10 @@ TEST(Singleton, SingletonConcurrency) {
auto func = [&gatekeeper]() {
gatekeeper.lock();
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);
......@@ -414,7 +512,7 @@ TEST(Singleton, SingletonCreationError) {
EXPECT_THROW(error_once_singleton.try_get(), std::runtime_error);
// second time it'll work fine
error_once_singleton.try_get();
error_once_singleton.apply([](auto* v) { return v; });
SUCCEED();
}
......@@ -429,7 +527,13 @@ TEST(Singleton, SingletonConcurrencyStress) {
std::vector<std::thread> ts;
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) {
......@@ -618,6 +722,42 @@ TEST(Singleton, MockTest) {
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
// Subprocess isn't currently supported under MSVC.
TEST(Singleton, DoubleRegistrationLogging) {
......@@ -660,6 +800,16 @@ TEST(Singleton, CustomCreator) {
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 {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonConcurrentCreationDestruction =
......@@ -672,9 +822,14 @@ struct SlowpokeNeedySingleton {
slowpokeNeedySingletonBaton.post();
/* sleep override */ std::this_thread::sleep_for(
std::chrono::milliseconds(100));
auto unused =
auto unused1 =
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) {
SingletonConcurrentCreationDestruction<SlowpokeNeedySingleton> needySingleton;
vault.registrationComplete();
std::thread needyThread([&] { needySingleton.try_get(); });
std::thread needyThread(
[&] { needySingleton.apply([](auto* v) { return v; }); });
slowpokeNeedySingletonBaton.wait();
......@@ -736,6 +892,29 @@ TEST(Singleton, MainThreadDestructor) {
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) {
// to keep track of calls to ctor and dtor below
struct Counts {
......@@ -784,6 +963,54 @@ TEST(Singleton, DoubleMakeMockAfterTryGet) {
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) {
struct PrivateTag {};
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