/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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.
*/
#include <folly/Singleton.h>
#include <cstdlib>
#include <thread>
#include <boost/thread/barrier.hpp>
#include <glog/logging.h>
#include <folly/experimental/io/FsUtil.h>
#include <folly/io/async/EventBase.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
#include <folly/test/SingletonTestStructs.h>
#include <folly/test/TestUtils.h>
FOLLY_GNU_DISABLE_WARNING("-Wdeprecated-declarations")
using namespace folly;
using namespace std::chrono_literals;
TEST(Singleton, MissingSingleton) {
EXPECT_DEATH(
[]() { auto u = Singleton<UnregisteredWatchdog>::try_get(); }(), "");
EXPECT_DEATH(
[]() {
Singleton<UnregisteredWatchdog>::apply([](auto* v) { return v; });
}(),
"");
}
struct BasicUsageTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonBasicUsage = Singleton<T, Tag, BasicUsageTag>;
// Exercise some basic codepaths ensuring registration order and
// destruction order happen as expected, that instances are created
// when expected, etc etc.
TEST(Singleton, BasicUsage) {
auto& vault = *SingletonVault::singleton<BasicUsageTag>();
EXPECT_EQ(vault.registeredSingletonCount(), 0);
SingletonBasicUsage<Watchdog> watchdog_singleton;
EXPECT_EQ(vault.registeredSingletonCount(), 1);
SingletonBasicUsage<ChildWatchdog> child_watchdog_singleton;
EXPECT_EQ(vault.registeredSingletonCount(), 2);
vault.registrationComplete();
// 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);
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);
}
vault.destroyInstances();
EXPECT_EQ(vault.registeredSingletonCount(), 2);
EXPECT_EQ(vault.livingSingletonCount(), 0);
}
struct DirectUsageTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonDirectUsage = Singleton<T, Tag, DirectUsageTag>;
TEST(Singleton, DirectUsage) {
auto& vault = *SingletonVault::singleton<DirectUsageTag>();
EXPECT_EQ(vault.registeredSingletonCount(), 0);
// Verify we can get to the underlying singletons via directly using
// the singleton definition.
SingletonDirectUsage<Watchdog> watchdog;
struct TestTag {};
SingletonDirectUsage<Watchdog, TestTag> named_watchdog;
EXPECT_EQ(vault.registeredSingletonCount(), 2);
vault.registrationComplete();
EXPECT_NE(watchdog.try_get(), nullptr);
EXPECT_EQ(watchdog.try_get(), SingletonDirectUsage<Watchdog>::try_get());
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();
}
struct NamedUsageTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonNamedUsage = Singleton<T, Tag, NamedUsageTag>;
TEST(Singleton, NamedUsage) {
auto& vault = *SingletonVault::singleton<NamedUsageTag>();
EXPECT_EQ(vault.registeredSingletonCount(), 0);
// Define two named Watchdog singletons and one unnamed singleton.
struct Watchdog1 {};
struct Watchdog2 {};
typedef detail::DefaultTag Watchdog3;
SingletonNamedUsage<Watchdog, Watchdog1> watchdog1_singleton;
EXPECT_EQ(vault.registeredSingletonCount(), 1);
SingletonNamedUsage<Watchdog, Watchdog2> watchdog2_singleton;
EXPECT_EQ(vault.registeredSingletonCount(), 2);
SingletonNamedUsage<Watchdog, Watchdog3> watchdog3_singleton;
EXPECT_EQ(vault.registeredSingletonCount(), 3);
vault.registrationComplete();
{
// Verify our three singletons are distinct and non-nullptr.
auto s1 = SingletonNamedUsage<Watchdog, Watchdog1>::try_get();
EXPECT_EQ(s1, watchdog1_singleton.try_get());
auto s2 = SingletonNamedUsage<Watchdog, Watchdog2>::try_get();
EXPECT_EQ(s2, watchdog2_singleton.try_get());
EXPECT_NE(s1, s2);
auto s3 = SingletonNamedUsage<Watchdog, Watchdog3>::try_get();
EXPECT_EQ(s3, watchdog3_singleton.try_get());
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();
}
struct NaughtyUsageTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonNaughtyUsage = Singleton<T, Tag, NaughtyUsageTag>;
struct NaughtyUsageTag2 {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonNaughtyUsage2 = Singleton<T, Tag, NaughtyUsageTag2>;
// Some pathological cases such as getting unregistered singletons,
// double registration, etc.
TEST(Singleton, NaughtyUsage) {
auto& vault = *SingletonVault::singleton<NaughtyUsageTag>();
const char* kBeforeRegistrationCompleteMsg =
".*Singleton .* requested before registrationComplete().*";
SingletonNaughtyUsage2<int> s0;
EXPECT_DEATH(s0.try_get(), kBeforeRegistrationCompleteMsg);
vault.registrationComplete();
const char* kUnregisteredMsg =
".*Creating instance for unregistered singleton.*";
EXPECT_DEATH(Singleton<Watchdog>::try_get(), kUnregisteredMsg);
EXPECT_DEATH(
Singleton<Watchdog>::apply([](auto* v) { return v; }), kUnregisteredMsg);
EXPECT_DEATH(SingletonNaughtyUsage<Watchdog>::try_get(), kUnregisteredMsg);
EXPECT_DEATH(
SingletonNaughtyUsage<Watchdog>::apply([](auto* v) { return v; }),
kUnregisteredMsg);
vault.destroyInstances();
auto& vault2 = *SingletonVault::singleton<NaughtyUsageTag2>();
EXPECT_DEATH(SingletonNaughtyUsage2<Watchdog>::try_get(), kUnregisteredMsg);
EXPECT_DEATH(
SingletonNaughtyUsage2<Watchdog>::apply([](auto* v) { return v; }),
kUnregisteredMsg);
SingletonNaughtyUsage2<Watchdog> watchdog_singleton;
const char* kDoubleRegistration =
"Double registration of singletons of the same underlying type";
EXPECT_DEATH(
[] { SingletonNaughtyUsage2<Watchdog> w2; }(), kDoubleRegistration);
vault2.destroyInstances();
// Double registration after destroy.
EXPECT_DEATH(
[] { SingletonNaughtyUsage2<Watchdog> w3; }(), kDoubleRegistration);
}
struct SharedPtrUsageTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonSharedPtrUsage = Singleton<T, Tag, SharedPtrUsageTag>;
// TODO (anob): revisit this test
TEST(Singleton, SharedPtrUsage) {
struct WatchdogHolder {
~WatchdogHolder() {
if (watchdog) {
LOG(ERROR) << "The following log message with stack trace is expected";
}
}
std::shared_ptr<Watchdog> watchdog;
};
auto& vault = *SingletonVault::singleton<SharedPtrUsageTag>();
EXPECT_EQ(vault.registeredSingletonCount(), 0);
std::vector<std::unique_ptr<Watchdog>> watchdog_instances;
SingletonSharedPtrUsage<Watchdog> watchdog_singleton(
[&] {
watchdog_instances.push_back(std::make_unique<Watchdog>());
return watchdog_instances.back().get();
},
[&](Watchdog* ptr) {
// Make sure that only second instance is destroyed. First instance is
// expected to be leaked.
EXPECT_EQ(watchdog_instances[1].get(), ptr);
watchdog_instances[1].reset();
});
EXPECT_EQ(vault.registeredSingletonCount(), 1);
SingletonSharedPtrUsage<ChildWatchdog> child_watchdog_singleton;
EXPECT_EQ(vault.registeredSingletonCount(), 2);
struct ATag {};
SingletonSharedPtrUsage<Watchdog, ATag> named_watchdog_singleton;
SingletonSharedPtrUsage<WatchdogHolder> watchdog_holder_singleton;
vault.registrationComplete();
// 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();
EXPECT_EQ(shared_s1.get(), s1);
auto old_serial = shared_s1->serial_number;
{
auto named_weak_s1 = SingletonSharedPtrUsage<Watchdog, ATag>::get_weak();
auto locked = named_weak_s1.lock();
EXPECT_NE(locked.get(), shared_s1.get());
}
// We should release externally locked shared_ptr, otherwise it will be
// considered a leak
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";
{
auto start_time = std::chrono::steady_clock::now();
vault.destroyInstances();
auto duration = std::chrono::steady_clock::now() - start_time;
EXPECT_TRUE(duration > std::chrono::seconds{4});
}
EXPECT_EQ(vault.registeredSingletonCount(), 4);
EXPECT_EQ(vault.livingSingletonCount(), 0);
EXPECT_TRUE(weak_s1.expired());
auto empty_s1 = SingletonSharedPtrUsage<Watchdog>::get_weak();
EXPECT_FALSE(empty_s1.lock());
vault.reenableInstances();
{
// 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();
auto new_s1_shared = new_s1_weak.lock();
std::thread t([new_s1_shared]() mutable {
std::this_thread::sleep_for(std::chrono::seconds{2});
new_s1_shared.reset();
});
new_s1_shared.reset();
{
auto start_time = std::chrono::steady_clock::now();
vault.destroyInstances();
auto duration = std::chrono::steady_clock::now() - start_time;
EXPECT_TRUE(
duration > std::chrono::seconds{1} &&
duration < std::chrono::seconds{3});
}
EXPECT_TRUE(new_s1_weak.expired());
t.join();
}
// Some classes to test singleton dependencies. NeedySingleton has a
// dependency on NeededSingleton, which happens during its
// construction.
struct NeedyTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonNeedy = Singleton<T, Tag, NeedyTag>;
struct NeededSingleton {};
struct NeedySingleton {
NeedySingleton() {
auto unused1 = SingletonNeedy<NeededSingleton>::try_get();
EXPECT_NE(unused1, nullptr);
auto unused2 =
SingletonNeedy<NeededSingleton>::apply([](auto* v) { return v; });
EXPECT_NE(unused2, nullptr);
}
};
// Ensure circular dependencies fail -- a singleton that needs itself, whoops.
struct SelfNeedyTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonSelfNeedy = Singleton<T, Tag, SelfNeedyTag>;
struct SelfNeedySingleton {
SelfNeedySingleton() {
auto unused1 = SingletonSelfNeedy<SelfNeedySingleton>::try_get();
EXPECT_NE(unused1, nullptr);
auto unused2 =
SingletonSelfNeedy<SelfNeedySingleton>::apply([](auto v) { return v; });
EXPECT_NE(unused2, nullptr);
}
};
TEST(Singleton, SingletonDependencies) {
SingletonNeedy<NeededSingleton> needed_singleton;
SingletonNeedy<NeedySingleton> needy_singleton;
auto& needy_vault = *SingletonVault::singleton<NeedyTag>();
needy_vault.registrationComplete();
EXPECT_EQ(needy_vault.registeredSingletonCount(), 2);
EXPECT_EQ(needy_vault.livingSingletonCount(), 0);
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
// properly wait for creation rather than thinking it is a circular
// dependency.
class Slowpoke : public Watchdog {
public:
Slowpoke() { std::this_thread::sleep_for(std::chrono::milliseconds(10)); }
};
struct ConcurrencyTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonConcurrency = Singleton<T, Tag, ConcurrencyTag>;
TEST(Singleton, SingletonConcurrency) {
auto& vault = *SingletonVault::singleton<ConcurrencyTag>();
SingletonConcurrency<Slowpoke> slowpoke_singleton;
vault.registrationComplete();
std::mutex gatekeeper;
gatekeeper.lock();
auto func = [&gatekeeper]() {
gatekeeper.lock();
gatekeeper.unlock();
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);
std::vector<std::thread> threads;
for (int i = 0; i < 100; ++i) {
threads.emplace_back(func);
}
// If circular dependency checks fail, the unlock would trigger a
// crash. Instead, it succeeds, and we have exactly one living
// singleton.
gatekeeper.unlock();
for (auto& t : threads) {
t.join();
}
EXPECT_EQ(vault.livingSingletonCount(), 1);
}
struct ErrorConstructor {
static size_t constructCount_;
ErrorConstructor() {
if ((constructCount_++) == 0) {
throw std::runtime_error("first time fails");
}
}
};
size_t ErrorConstructor::constructCount_(0);
struct CreationErrorTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonCreationError = Singleton<T, Tag, CreationErrorTag>;
TEST(Singleton, SingletonCreationError) {
SingletonCreationError<ErrorConstructor> error_once_singleton;
SingletonVault::singleton<CreationErrorTag>()->registrationComplete();
// first time should error out
EXPECT_THROW(error_once_singleton.try_get(), std::runtime_error);
// second time it'll work fine
error_once_singleton.apply([](auto* v) { return v; });
SUCCEED();
}
struct ConcurrencyStressTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonConcurrencyStress = Singleton<T, Tag, ConcurrencyStressTag>;
TEST(Singleton, SingletonConcurrencyStress) {
auto& vault = *SingletonVault::singleton<ConcurrencyStressTag>();
SingletonConcurrencyStress<Slowpoke> slowpoke_singleton;
vault.registrationComplete();
std::vector<std::thread> ts;
for (size_t i = 0; i < 100; ++i) {
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) {
std::chrono::milliseconds d(20);
std::this_thread::sleep_for(d);
vault.destroyInstances();
std::this_thread::sleep_for(d);
vault.destroyInstances();
}
for (auto& t : ts) {
t.join();
}
}
namespace {
struct EagerInitSyncTag {};
} // namespace
template <typename T, typename Tag = detail::DefaultTag>
using SingletonEagerInitSync = Singleton<T, Tag, EagerInitSyncTag>;
TEST(Singleton, SingletonEagerInitSync) {
auto& vault = *SingletonVault::singleton<EagerInitSyncTag>();
bool didEagerInit = false;
auto sing = SingletonEagerInitSync<std::string>([&] {
didEagerInit = true;
return new std::string("foo");
}).shouldEagerInit();
vault.registrationComplete();
EXPECT_FALSE(didEagerInit);
vault.doEagerInit();
EXPECT_TRUE(didEagerInit);
sing.get_weak(); // (avoid compile error complaining about unused var 'sing')
}
namespace {
struct EagerInitAsyncTag {};
} // namespace
template <typename T, typename Tag = detail::DefaultTag>
using SingletonEagerInitAsync = Singleton<T, Tag, EagerInitAsyncTag>;
TEST(Singleton, SingletonEagerInitAsync) {
auto& vault = *SingletonVault::singleton<EagerInitAsyncTag>();
bool didEagerInit = false;
auto sing = SingletonEagerInitAsync<std::string>([&] {
didEagerInit = true;
return new std::string("foo");
}).shouldEagerInit();
folly::EventBase eb;
folly::Baton<> done;
vault.registrationComplete();
EXPECT_FALSE(didEagerInit);
vault.doEagerInitVia(eb, &done);
eb.loop();
done.wait();
EXPECT_TRUE(didEagerInit);
sing.get_weak(); // (avoid compile error complaining about unused var 'sing')
}
namespace {
class TestEagerInitParallelExecutor : public folly::Executor {
public:
explicit TestEagerInitParallelExecutor(const size_t threadCount) {
eventBases_.reserve(threadCount);
threads_.reserve(threadCount);
for (size_t i = 0; i < threadCount; i++) {
eventBases_.push_back(std::make_shared<folly::EventBase>());
auto eb = eventBases_.back();
threads_.emplace_back(
std::make_shared<std::thread>([eb] { eb->loopForever(); }));
}
}
~TestEagerInitParallelExecutor() override {
for (auto eb : eventBases_) {
eb->runInEventBaseThread([eb] { eb->terminateLoopSoon(); });
}
for (auto thread : threads_) {
thread->join();
}
}
void add(folly::Func func) override {
const auto index = (counter_++) % eventBases_.size();
eventBases_[index]->add(std::move(func));
}
private:
std::vector<std::shared_ptr<folly::EventBase>> eventBases_;
std::vector<std::shared_ptr<std::thread>> threads_;
std::atomic<size_t> counter_{0};
};
} // namespace
namespace {
struct EagerInitParallelTag {};
} // namespace
template <typename T, typename Tag = detail::DefaultTag>
using SingletonEagerInitParallel = Singleton<T, Tag, EagerInitParallelTag>;
TEST(Singleton, SingletonEagerInitParallel) {
const static size_t kIters = 1000;
const static size_t kThreads = 20;
std::atomic<size_t> initCounter{0};
auto& vault = *SingletonVault::singleton<EagerInitParallelTag>();
auto sing = SingletonEagerInitParallel<std::string>([&] {
++initCounter;
return new std::string("");
}).shouldEagerInit();
for (size_t i = 0; i < kIters; i++) {
SCOPE_EXIT {
// clean up each time
vault.destroyInstances();
vault.reenableInstances();
};
initCounter.store(0);
{
std::vector<std::shared_ptr<std::thread>> threads;
boost::barrier barrier(kThreads);
TestEagerInitParallelExecutor exe(kThreads);
vault.registrationComplete();
EXPECT_EQ(0, initCounter.load());
for (size_t j = 0; j < kThreads; j++) {
threads.push_back(std::make_shared<std::thread>([&] {
barrier.wait();
vault.doEagerInitVia(exe);
}));
}
for (auto thread : threads) {
thread->join();
}
}
EXPECT_EQ(1, initCounter.load());
sing.get_weak(); // (avoid compile error complaining about unused var)
}
}
struct StateTestTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonVaultStateTest = Singleton<T, Tag, StateTestTag>;
TEST(Singleton, SingletonVaultStateTest) {
auto& vault = *SingletonVault::singleton<StateTestTag>();
// vault must not be disabled after construction
EXPECT_FALSE(vault.isDisabled());
EXPECT_EQ(vault.registeredSingletonCount(), 0);
SingletonVaultStateTest<Watchdog> watchdog_singleton;
EXPECT_EQ(vault.registeredSingletonCount(), 1);
// vault must not be disabled after adding a singleton
EXPECT_FALSE(vault.isDisabled());
vault.registrationComplete();
// vault must not be disabled after registration is complete
EXPECT_FALSE(vault.isDisabled());
vault.destroyInstances();
// vault must be disabled after destroying instances
EXPECT_TRUE(vault.isDisabled());
vault.reenableInstances();
// vault must not be disabled after reenabling instances
EXPECT_FALSE(vault.isDisabled());
EXPECT_EQ(vault.registeredSingletonCount(), 1);
}
struct MockTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonMock = Singleton<T, Tag, MockTag>;
// Verify that existing Singleton's can be overridden
// using the make_mock functionality.
TEST(Singleton, MockTest) {
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>::try_get()->serial_number;
// Override existing mock using make_mock.
SingletonMock<Watchdog>::make_mock();
EXPECT_EQ(vault.registeredSingletonCount(), 1);
int serial_count_mock = SingletonMock<Watchdog>::try_get()->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>::try_get()->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();
}
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();
}
// Singleton using a non default constructor test/example:
struct X {
X() : X(-1, "unset") {}
X(int a1_, std::string a2_) : a1(a1_), a2(a2_) {
LOG(INFO) << "X(" << a1 << "," << a2 << ")";
}
const int a1;
const std::string a2;
};
folly::Singleton<X> singleton_x([]() { return new X(42, "foo"); });
TEST(Singleton, CustomCreator) {
X x1;
std::shared_ptr<X> x2p = singleton_x.try_get();
EXPECT_NE(nullptr, x2p);
EXPECT_NE(x1.a1, x2p->a1);
EXPECT_NE(x1.a2, x2p->a2);
EXPECT_EQ(42, x2p->a1);
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 =
Singleton<T, Tag, ConcurrentCreationDestructionTag>;
folly::Baton<> slowpokeNeedySingletonBaton;
struct SlowpokeNeedySingleton {
SlowpokeNeedySingleton() {
slowpokeNeedySingletonBaton.post();
/* sleep override */ std::this_thread::sleep_for(
std::chrono::milliseconds(100));
auto unused1 =
SingletonConcurrentCreationDestruction<NeededSingleton>::try_get();
EXPECT_NE(unused1, nullptr);
auto unused2 =
SingletonConcurrentCreationDestruction<NeededSingleton>::apply(
[](auto* v) { return v; });
EXPECT_NE(unused2, nullptr);
}
};
TEST(Singleton, ConcurrentCreationDestruction) {
auto& vault = *SingletonVault::singleton<ConcurrentCreationDestructionTag>();
SingletonConcurrentCreationDestruction<NeededSingleton> neededSingleton;
SingletonConcurrentCreationDestruction<SlowpokeNeedySingleton> needySingleton;
vault.registrationComplete();
std::thread needyThread(
[&] { needySingleton.apply([](auto* v) { return v; }); });
slowpokeNeedySingletonBaton.wait();
vault.destroyInstances();
needyThread.join();
}
struct MainThreadDestructorTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonMainThreadDestructor =
Singleton<T, Tag, MainThreadDestructorTag>;
struct ThreadLoggingSingleton {
ThreadLoggingSingleton() { initThread = std::this_thread::get_id(); }
~ThreadLoggingSingleton() { destroyThread = std::this_thread::get_id(); }
static std::thread::id initThread;
static std::thread::id destroyThread;
};
std::thread::id ThreadLoggingSingleton::initThread{};
std::thread::id ThreadLoggingSingleton::destroyThread{};
TEST(Singleton, MainThreadDestructor) {
auto& vault = *SingletonVault::singleton<MainThreadDestructorTag>();
SingletonMainThreadDestructor<ThreadLoggingSingleton> singleton;
vault.registrationComplete();
EXPECT_EQ(std::thread::id(), ThreadLoggingSingleton::initThread);
singleton.try_get();
EXPECT_EQ(std::this_thread::get_id(), ThreadLoggingSingleton::initThread);
std::thread t([instance = singleton.try_get()] {
/* 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, 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 {
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::try_get();
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, 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;
auto* ptr0 = &gPtr.get();
EXPECT_EQ(*ptr0, 0);
gPtr.make_mock([] { return new int(1); });
auto* ptr1 = &gPtr.get();
EXPECT_NE(ptr0, ptr1);
EXPECT_EQ(*ptr1, 1);
}
TEST(Singleton, LeakySingletonTSAN) {
struct PrivateTag {};
static folly::LeakySingleton<int, PrivateTag> gPtr;
auto* ptr0 = &gPtr.get();
EXPECT_EQ(*ptr0, 0);
auto func = [&]() {
auto val = gPtr.get();
EXPECT_TRUE(val == 0 || val == 1);
};
std::vector<std::thread> threads;
for (int i = 0; i < 100; i++) {
threads.emplace_back(func);
}
gPtr.make_mock([] { return new int(1); });
for (auto& t : threads) {
t.join();
}
}
TEST(Singleton, ShutdownTimer) {
// TSAN will SIGSEGV if the shutdown timer activates (it spawns a new thread,
// which TSAN doesn't like).
SKIP_IF(folly::kIsSanitizeThread);
struct VaultTag {};
struct PrivateTag {};
struct Object {
~Object() {
/* sleep override */ std::this_thread::sleep_for(shutdownDuration);
}
std::chrono::milliseconds shutdownDuration;
};
using SingletonObject = Singleton<Object, PrivateTag, VaultTag>;
auto& vault = *SingletonVault::singleton<VaultTag>();
SingletonObject object;
vault.registrationComplete();
vault.setShutdownTimeout(10ms);
SingletonObject::try_get()->shutdownDuration = 10s;
EXPECT_DEATH(
[&]() { vault.destroyInstancesFinal(); }(),
"Failed to complete shutdown within 10ms.");
vault.setShutdownTimeout(10s);
SingletonObject::try_get()->shutdownDuration = 10ms;
vault.destroyInstancesFinal();
}
TEST(Singleton, ShutdownTimerDisable) {
struct VaultTag {};
struct PrivateTag {};
struct Object {
~Object() {
/* sleep override */ std::this_thread::sleep_for(shutdownDuration);
}
std::chrono::milliseconds shutdownDuration;
};
using SingletonObject = Singleton<Object, PrivateTag, VaultTag>;
auto& vault = *SingletonVault::singleton<VaultTag>();
SingletonObject object;
vault.registrationComplete();
vault.disableShutdownTimeout();
SingletonObject::try_get()->shutdownDuration = 100ms;
vault.destroyInstancesFinal();
}
TEST(Singleton, ForkInChild) {
struct VaultTag {};
struct PrivateTag {};
struct ForkObject {};
using SingletonObject = Singleton<ForkObject, PrivateTag, VaultTag>;
auto& vault = *SingletonVault::singleton<VaultTag>();
vault.setFailOnUseAfterFork(true);
SingletonObject object;
vault.registrationComplete();
// We use EXPECT_DEATH here to run code in the child process.
EXPECT_DEATH(
[&]() {
object.try_get();
vault.destroyInstances();
LOG(FATAL) << "Finished successfully";
}(),
"Finished successfully");
object.try_get();
if (!folly::kIsDebug) {
return;
}
EXPECT_DEATH(
[&]() { object.try_get(); }(),
"Attempting to use singleton .*ForkObject.* in child process");
EXPECT_DEATH(
[&]() { vault.destroyInstances(); }(),
"Attempting to destroy singleton .*ForkObject.* in child process");
}
struct EagerInitOnReenableSingletonsTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonEagerInitOnReenableSingletons =
Singleton<T, Tag, EagerInitOnReenableSingletonsTag>;
TEST(Singleton, EagerInitOnReenableSingletons) {
struct CountingSingleton {
explicit CountingSingleton(int& counter) { ++counter; }
};
int counter1{0};
int counter2{0};
struct Tag1 {};
struct Tag2 {};
auto& vault = *SingletonVault::singleton<EagerInitOnReenableSingletonsTag>();
auto singleton1 =
SingletonEagerInitOnReenableSingletons<CountingSingleton, Tag1>([&] {
return new CountingSingleton(counter1);
}).shouldEagerInitOnReenable();
auto singleton2 =
SingletonEagerInitOnReenableSingletons<CountingSingleton, Tag2>([&] {
return new CountingSingleton(counter2);
}).shouldEagerInitOnReenable();
vault.registrationComplete();
EXPECT_EQ(0, counter1);
EXPECT_EQ(0, counter2);
singleton1.try_get();
EXPECT_EQ(1, counter1);
EXPECT_EQ(0, counter2);
vault.destroyInstances();
vault.reenableInstances();
EXPECT_EQ(2, counter1);
EXPECT_EQ(0, counter2);
singleton1.try_get();
EXPECT_EQ(2, counter1);
EXPECT_EQ(0, counter2);
vault.destroyInstances();
vault.reenableInstances();
EXPECT_EQ(3, counter1);
EXPECT_EQ(0, counter2);
singleton2.try_get();
EXPECT_EQ(3, counter1);
EXPECT_EQ(1, counter2);
vault.destroyInstances();
vault.reenableInstances();
EXPECT_EQ(4, counter1);
EXPECT_EQ(2, counter2);
}
namespace {
class CancelOnDestructionSingleton {
public:
~CancelOnDestructionSingleton() {
CHECK(SingletonVault::singleton()
->getDestructionCancellationToken()
.isCancellationRequested());
}
};
auto cancelOnDestructionSingleton =
folly::Singleton<CancelOnDestructionSingleton>{};
} // namespace
TEST(Singleton, CancelOnDestruction) {
cancelOnDestructionSingleton.try_get();
}