/*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <thread>
+#include <boost/thread/barrier.hpp>
+#include <glog/logging.h>
+
#include <folly/Singleton.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/Benchmark.h>
-#include <glog/logging.h>
-#include <gtest/gtest.h>
-#include <boost/thread/barrier.hpp>
+#ifndef _MSC_VER
+#include <folly/Subprocess.h>
+#endif
+
+FOLLY_GCC_DISABLE_WARNING("-Wdeprecated-declarations")
using namespace folly;
EXPECT_NE(s2, nullptr);
EXPECT_EQ(s1, s2);
+ EXPECT_EQ(s1.get(), SingletonBasicUsage<Watchdog>::try_get_fast().get());
std::shared_ptr<ChildWatchdog> s3 =
SingletonBasicUsage<ChildWatchdog>::try_get();
SingletonNaughtyUsage2<Watchdog> watchdog_singleton;
// double registration
- EXPECT_DEATH([]() { SingletonNaughtyUsage2<Watchdog> watchdog_singleton; }(),
- "");
+ EXPECT_DEATH([]() { SingletonNaughtyUsage2<Watchdog> w2; }(), "");
vault2.destroyInstances();
// double registration after destroy
- EXPECT_DEATH([]() { SingletonNaughtyUsage2<Watchdog> watchdog_singleton; }(),
- "");
+ EXPECT_DEATH([]() { SingletonNaughtyUsage2<Watchdog> w3; }(), "");
}
struct SharedPtrUsageTag {};
auto& vault = *SingletonVault::singleton<SharedPtrUsageTag>();
EXPECT_EQ(vault.registeredSingletonCount(), 0);
- SingletonSharedPtrUsage<Watchdog> watchdog_singleton;
+ 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;
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} &&
- duration < std::chrono::seconds{6});
+ EXPECT_TRUE(
+ duration > std::chrono::seconds{4} &&
+ duration < std::chrono::seconds{folly::kIsSanitizeAddress ? 30 : 6});
}
EXPECT_EQ(vault.registeredSingletonCount(), 4);
EXPECT_EQ(vault.livingSingletonCount(), 0);
using SingletonCreationError = Singleton<T, Tag, CreationErrorTag>;
TEST(Singleton, SingletonCreationError) {
- SingletonVault::singleton<CreationErrorTag>();
SingletonCreationError<ErrorConstructor> error_once_singleton;
+ SingletonVault::singleton<CreationErrorTag>()->registrationComplete();
// first time should error out
EXPECT_THROW(error_once_singleton.try_get(), std::runtime_error);
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) {
namespace {
struct EagerInitSyncTag {};
-}
+} // namespace
template <typename T, typename Tag = detail::DefaultTag>
using SingletonEagerInitSync = Singleton<T, Tag, EagerInitSyncTag>;
TEST(Singleton, SingletonEagerInitSync) {
namespace {
struct EagerInitAsyncTag {};
-}
+} // namespace
template <typename T, typename Tag = detail::DefaultTag>
using SingletonEagerInitAsync = Singleton<T, Tag, EagerInitAsyncTag>;
TEST(Singleton, SingletonEagerInitAsync) {
}
}
- virtual ~TestEagerInitParallelExecutor() override {
+ ~TestEagerInitParallelExecutor() override {
for (auto eb : eventBases_) {
eb->runInEventBaseThread([eb] { eb->terminateLoopSoon(); });
}
}
}
- virtual void add(folly::Func func) override {
+ void add(folly::Func func) override {
const auto index = (counter_ ++) % eventBases_.size();
- eventBases_[index]->add(func);
+ eventBases_[index]->add(std::move(func));
}
private:
std::vector<std::shared_ptr<std::thread>> threads_;
std::atomic<size_t> counter_ {0};
};
-} // namespace
+} // namespace
namespace {
struct EagerInitParallelTag {};
-}
+} // namespace
template <typename T, typename Tag = detail::DefaultTag>
using SingletonEagerInitParallel = Singleton<T, Tag, EagerInitParallelTag>;
TEST(Singleton, SingletonEagerInitParallel) {
}
}
-// Benchmarking a normal singleton vs a Meyers singleton vs a Folly
-// singleton. Meyers are insanely fast, but (hopefully) Folly
-// singletons are fast "enough."
-int* getMeyersSingleton() {
- static auto ret = new int(0);
- return ret;
-}
-
-int normal_singleton_value = 0;
-int* getNormalSingleton() {
- doNotOptimizeAway(&normal_singleton_value);
- return &normal_singleton_value;
-}
-
struct MockTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonMock = Singleton <T, Tag, MockTag>;
// If serial_count value is the same, then singleton was not replaced.
EXPECT_NE(serial_count_first, serial_count_mock);
-}
-struct BenchmarkSingleton {
- int val = 0;
-};
+ // Override existing mock using make_mock one more time
+ SingletonMock<Watchdog>::make_mock();
-void run4Threads(std::function<void()> f) {
- std::vector<std::thread> threads;
- for (size_t i = 0; i < 4; ++ i) {
- threads.emplace_back(f);
- }
- for (auto& thread : threads) {
- thread.join();
- }
+ 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();
}
-void normalSingleton(size_t n) {
- for (size_t i = 0; i < n; ++ i) {
- doNotOptimizeAway(getNormalSingleton());
+#ifndef _MSC_VER
+// Subprocess isn't currently supported under MSVC.
+TEST(Singleton, DoubleRegistrationLogging) {
+ const auto basename = "singleton_double_registration";
+ const auto sub = fs::executable_path().remove_filename() / basename;
+ auto p = Subprocess(
+ std::vector<std::string>{sub.string()},
+ Subprocess::Options()
+ .stdinFd(Subprocess::CLOSE)
+ .stdoutFd(Subprocess::CLOSE)
+ .pipeStderr()
+ .closeOtherFds());
+ auto err = p.communicate("").second;
+ auto res = p.wait();
+ EXPECT_EQ(ProcessReturnCode::KILLED, res.state());
+ EXPECT_EQ(SIGABRT, res.killSignal());
+ EXPECT_THAT(err, testing::StartsWith("Double registration of singletons"));
+}
+#endif
+
+// 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;
+};
-BENCHMARK(NormalSingleton, n) {
- normalSingleton(n);
-}
+folly::Singleton<X> singleton_x([]() { return new X(42, "foo"); });
-BENCHMARK(NormalSingleton4Threads, n) {
- run4Threads([=]() {
- normalSingleton(n);
- });
+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);
}
-void meyersSingleton(size_t n) {
- for (size_t i = 0; i < n; ++i) {
- doNotOptimizeAway(getMeyersSingleton());
+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 unused =
+ SingletonConcurrentCreationDestruction<NeededSingleton>::try_get();
+ EXPECT_NE(unused, nullptr);
}
-}
+};
+TEST(Singleton, ConcurrentCreationDestruction) {
+ auto& vault = *SingletonVault::singleton<ConcurrentCreationDestructionTag>();
+ SingletonConcurrentCreationDestruction<NeededSingleton> neededSingleton;
+ SingletonConcurrentCreationDestruction<SlowpokeNeedySingleton> needySingleton;
+ vault.registrationComplete();
-BENCHMARK_RELATIVE(MeyersSingleton, n) {
- meyersSingleton(n);
-}
+ std::thread needyThread([&] { needySingleton.try_get(); });
-BENCHMARK_RELATIVE(MeyersSingleton4Threads, n) {
- run4Threads([=]() {
- meyersSingleton(n);
- });
+ slowpokeNeedySingletonBaton.wait();
+
+ vault.destroyInstances();
+
+ needyThread.join();
}
-struct BenchmarkTag {};
+struct MainThreadDestructorTag {};
template <typename T, typename Tag = detail::DefaultTag>
-using SingletonBenchmark = Singleton <T, Tag, BenchmarkTag>;
+using SingletonMainThreadDestructor =
+ Singleton<T, Tag, MainThreadDestructorTag>;
-struct GetTag{};
-struct GetSharedTag{};
-struct GetWeakTag{};
-
-SingletonBenchmark<BenchmarkSingleton, GetTag> benchmark_singleton_get;
-SingletonBenchmark<BenchmarkSingleton, GetSharedTag>
-benchmark_singleton_get_shared;
-SingletonBenchmark<BenchmarkSingleton, GetWeakTag> benchmark_singleton_get_weak;
+struct ThreadLoggingSingleton {
+ ThreadLoggingSingleton() {
+ initThread = std::this_thread::get_id();
+ }
-void follySingletonRaw(size_t n) {
- for (size_t i = 0; i < n; ++i) {
- SingletonBenchmark<BenchmarkSingleton, GetTag>::get();
+ ~ThreadLoggingSingleton() {
+ destroyThread = std::this_thread::get_id();
}
-}
-BENCHMARK_RELATIVE(FollySingletonRaw, n) {
- follySingletonRaw(n);
-}
+ static std::thread::id initThread;
+ static std::thread::id destroyThread;
+};
+std::thread::id ThreadLoggingSingleton::initThread{};
+std::thread::id ThreadLoggingSingleton::destroyThread{};
-BENCHMARK_RELATIVE(FollySingletonRaw4Threads, n) {
- run4Threads([=]() {
- follySingletonRaw(n);
- });
-}
+TEST(Singleton, MainThreadDestructor) {
+ auto& vault = *SingletonVault::singleton<MainThreadDestructorTag>();
+ SingletonMainThreadDestructor<ThreadLoggingSingleton> singleton;
-void follySingletonSharedPtr(size_t n) {
- for (size_t i = 0; i < n; ++i) {
- SingletonBenchmark<BenchmarkSingleton, GetSharedTag>::try_get();
- }
-}
+ vault.registrationComplete();
+ EXPECT_EQ(std::thread::id(), ThreadLoggingSingleton::initThread);
-BENCHMARK_RELATIVE(FollySingletonSharedPtr, n) {
- follySingletonSharedPtr(n);
-}
+ singleton.try_get();
+ EXPECT_EQ(std::this_thread::get_id(), ThreadLoggingSingleton::initThread);
-BENCHMARK_RELATIVE(FollySingletonSharedPtr4Threads, n) {
- run4Threads([=]() {
- follySingletonSharedPtr(n);
- });
-}
+ std::thread t([instance = singleton.try_get()] {
+ /* sleep override */ std::this_thread::sleep_for(
+ std::chrono::milliseconds{100});
+ });
-void follySingletonWeakPtr(size_t n) {
- for (size_t i = 0; i < n; ++i) {
- SingletonBenchmark<BenchmarkSingleton, GetWeakTag>::get_weak();
- }
-}
+ EXPECT_EQ(std::thread::id(), ThreadLoggingSingleton::destroyThread);
-BENCHMARK_RELATIVE(FollySingletonWeakPtr, n) {
- follySingletonWeakPtr(n);
-}
+ vault.destroyInstances();
+ EXPECT_EQ(std::this_thread::get_id(), ThreadLoggingSingleton::destroyThread);
-BENCHMARK_RELATIVE(FollySingletonWeakPtr4Threads, n) {
- run4Threads([=]() {
- follySingletonWeakPtr(n);
- });
+ t.join();
}
-int main(int argc, char* argv[]) {
- testing::InitGoogleTest(&argc, argv);
- google::InitGoogleLogging(argv[0]);
- gflags::ParseCommandLineFlags(&argc, &argv, true);
+TEST(Singleton, DoubleMakeMockAfterTryGet) {
+ // to keep track of calls to ctor and dtor below
+ struct Counts {
+ size_t ctor = 0;
+ size_t dtor = 0;
+ };
- SingletonVault::singleton()->registrationComplete();
+ // 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>;
- auto ret = RUN_ALL_TESTS();
- if (!ret) {
- folly::runBenchmarksOnFlag();
- }
- return ret;
+ // 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);
}