X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Ftest%2FSingletonTest.cpp;h=be95ea4dfef9a6496dee19d62f3a1a9de5bd2a60;hb=8e110a76171f5204d63ce3dbd283589f8914fe44;hp=575e136f5c445b4c8c93005d1a06bdab3d6b4ffe;hpb=faa75f83a86b59f9107d244b568137ddc4270f8a;p=folly.git diff --git a/folly/test/SingletonTest.cpp b/folly/test/SingletonTest.cpp index 575e136f..be95ea4d 100644 --- a/folly/test/SingletonTest.cpp +++ b/folly/test/SingletonTest.cpp @@ -1,5 +1,5 @@ /* - * 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. @@ -17,14 +17,21 @@ #include #include +#include #include +#include +#include #include -#include + +#ifndef _MSC_VER +#include +#endif #include -#include #include +FOLLY_GCC_DISABLE_WARNING("-Wdeprecated-declarations") + using namespace folly; TEST(Singleton, MissingSingleton) { @@ -60,6 +67,7 @@ TEST(Singleton, BasicUsage) { EXPECT_NE(s2, nullptr); EXPECT_EQ(s1, s2); + EXPECT_EQ(s1.get(), SingletonBasicUsage::try_get_fast().get()); std::shared_ptr s3 = SingletonBasicUsage::try_get(); @@ -168,13 +176,11 @@ TEST(Singleton, NaughtyUsage) { SingletonNaughtyUsage2 watchdog_singleton; // double registration - EXPECT_DEATH([]() { SingletonNaughtyUsage2 watchdog_singleton; }(), - ""); + EXPECT_DEATH([]() { SingletonNaughtyUsage2 w2; }(), ""); vault2.destroyInstances(); // double registration after destroy - EXPECT_DEATH([]() { SingletonNaughtyUsage2 watchdog_singleton; }(), - ""); + EXPECT_DEATH([]() { SingletonNaughtyUsage2 w3; }(), ""); } struct SharedPtrUsageTag {}; @@ -196,7 +202,18 @@ TEST(Singleton, SharedPtrUsage) { auto& vault = *SingletonVault::singleton(); EXPECT_EQ(vault.registeredSingletonCount(), 0); - SingletonSharedPtrUsage watchdog_singleton; + std::vector> watchdog_instances; + SingletonSharedPtrUsage watchdog_singleton( + [&] { + watchdog_instances.push_back(std::make_unique()); + 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 child_watchdog_singleton; @@ -387,14 +404,14 @@ template using SingletonCreationError = Singleton; TEST(Singleton, SingletonCreationError) { - SingletonVault::singleton(); SingletonCreationError error_once_singleton; + SingletonVault::singleton()->registrationComplete(); // first time should error out - EXPECT_THROW(error_once_singleton.get_weak().lock(), std::runtime_error); + EXPECT_THROW(error_once_singleton.try_get(), std::runtime_error); // second time it'll work fine - error_once_singleton.get_weak().lock(); + error_once_singleton.try_get(); SUCCEED(); } @@ -405,11 +422,12 @@ using SingletonConcurrencyStress = Singleton ; TEST(Singleton, SingletonConcurrencyStress) { auto& vault = *SingletonVault::singleton(); SingletonConcurrencyStress slowpoke_singleton; + vault.registrationComplete(); std::vector ts; for (size_t i = 0; i < 100; ++i) { ts.emplace_back([&]() { - slowpoke_singleton.get_weak().lock(); + slowpoke_singleton.try_get(); }); } @@ -481,7 +499,7 @@ class TestEagerInitParallelExecutor : public folly::Executor { } } - virtual ~TestEagerInitParallelExecutor() override { + ~TestEagerInitParallelExecutor() override { for (auto eb : eventBases_) { eb->runInEventBaseThread([eb] { eb->terminateLoopSoon(); }); } @@ -490,9 +508,9 @@ class TestEagerInitParallelExecutor : public folly::Executor { } } - 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: @@ -554,20 +572,6 @@ 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 using SingletonMock = Singleton ; @@ -593,126 +597,182 @@ TEST(Singleton, MockTest) { // 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::make_mock(); -void run4Threads(std::function f) { - std::vector 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::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{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 singleton_x([]() { return new X(42, "foo"); }); -BENCHMARK(NormalSingleton4Threads, n) { - run4Threads([=]() { - normalSingleton(n); - }); +TEST(Singleton, CustomCreator) { + X x1; + std::shared_ptr 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 +using SingletonConcurrentCreationDestruction = + Singleton; + +folly::Baton<> slowpokeNeedySingletonBaton; + +struct SlowpokeNeedySingleton { + SlowpokeNeedySingleton() { + slowpokeNeedySingletonBaton.post(); + /* sleep override */ std::this_thread::sleep_for( + std::chrono::milliseconds(100)); + auto unused = + SingletonConcurrentCreationDestruction::try_get(); + EXPECT_NE(unused, nullptr); } -} +}; +TEST(Singleton, ConcurrentCreationDestruction) { + auto& vault = *SingletonVault::singleton(); + SingletonConcurrentCreationDestruction neededSingleton; + SingletonConcurrentCreationDestruction 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 -using SingletonBenchmark = Singleton ; +using SingletonMainThreadDestructor = + Singleton; -struct GetTag{}; -struct GetSharedTag{}; -struct GetWeakTag{}; - -SingletonBenchmark benchmark_singleton_get; -SingletonBenchmark -benchmark_singleton_get_shared; -SingletonBenchmark 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::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(); + SingletonMainThreadDestructor singleton; -void follySingletonSharedPtr(size_t n) { - for (size_t i = 0; i < n; ++i) { - SingletonBenchmark::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::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; - auto ret = RUN_ALL_TESTS(); - if (!ret) { - folly::runBenchmarksOnFlag(); - } - return ret; + // register everything + Counts counts; + auto& vault = *SingletonVault::singleton(); + 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); }