X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Ftest%2FSingletonTest.cpp;h=7ed6f1074bae76ef24ce43cccd9ff784810e2393;hb=a9d90ea14f527b0b719a90f014c8c20e13d54866;hp=55152751567bcc7d00a7849aabc1f8afb943ea2a;hpb=8f1e662a59dec3476bf626b283effaa8a88acdd5;p=folly.git diff --git a/folly/test/SingletonTest.cpp b/folly/test/SingletonTest.cpp index 55152751..7ed6f107 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) { - auto& vault = *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(); }); } @@ -457,11 +475,12 @@ TEST(Singleton, SingletonEagerInitAsync) { [&] {didEagerInit = true; return new std::string("foo"); }) .shouldEagerInit(); folly::EventBase eb; + folly::Baton<> done; vault.registrationComplete(); EXPECT_FALSE(didEagerInit); - auto result = vault.doEagerInitVia(&eb); // a Future is returned + vault.doEagerInitVia(eb, &done); eb.loop(); - result.get(); // ensure this completed successfully and didn't hang forever + done.wait(); EXPECT_TRUE(didEagerInit); sing.get_weak(); // (avoid compile error complaining about unused var 'sing') } @@ -491,7 +510,7 @@ class TestEagerInitParallelExecutor : public folly::Executor { virtual void add(folly::Func func) override { const auto index = (counter_ ++) % eventBases_.size(); - eventBases_[index]->add(func); + eventBases_[index]->add(std::move(func)); } private: @@ -538,7 +557,7 @@ TEST(Singleton, SingletonEagerInitParallel) { for (size_t j = 0; j < kThreads; j++) { threads.push_back(std::make_shared([&] { barrier.wait(); - vault.doEagerInitVia(&exe).get(); + vault.doEagerInitVia(exe); })); } @@ -553,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 ; @@ -592,56 +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(); -BENCHMARK(NormalSingleton, n) { - for (size_t i = 0; i < n; ++i) { - doNotOptimizeAway(getNormalSingleton()); - } + 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(); } -BENCHMARK_RELATIVE(MeyersSingleton, n) { - for (size_t i = 0; i < n; ++i) { - doNotOptimizeAway(getMeyersSingleton()); +#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; +}; + +folly::Singleton singleton_x([]() { return new X(42, "foo"); }); + +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); } -struct BenchmarkTag {}; +struct ConcurrentCreationDestructionTag {}; template -using SingletonBenchmark = Singleton ; +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); + } +}; -struct GetTag{}; -struct GetWeakTag{}; +TEST(Singleton, ConcurrentCreationDestruction) { + auto& vault = *SingletonVault::singleton(); + SingletonConcurrentCreationDestruction neededSingleton; + SingletonConcurrentCreationDestruction needySingleton; + vault.registrationComplete(); -SingletonBenchmark benchmark_singleton_get; -SingletonBenchmark benchmark_singleton_get_weak; + std::thread needyThread([&] { needySingleton.try_get(); }); -BENCHMARK_RELATIVE(FollySingleton, n) { - for (size_t i = 0; i < n; ++i) { - SingletonBenchmark::try_get(); - } + slowpokeNeedySingletonBaton.wait(); + + vault.destroyInstances(); + + needyThread.join(); } -BENCHMARK_RELATIVE(FollySingletonWeak, n) { - for (size_t i = 0; i < n; ++i) { - SingletonBenchmark::get_weak(); +struct MainThreadDestructorTag {}; +template +using SingletonMainThreadDestructor = + Singleton; + +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(); + SingletonMainThreadDestructor 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(); } -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; + }; + + // 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; + + // register everything + Counts counts; + auto& vault = *SingletonVault::singleton(); + auto new_object = [&] { return new Object(counts); }; + SingletonObject object_(new_object); + vault.registrationComplete(); - SingletonVault::singleton()->registrationComplete(); + // no eager inits, nada (sanity) + EXPECT_EQ(0, counts.ctor); + EXPECT_EQ(0, counts.dtor); - auto ret = RUN_ALL_TESTS(); - if (!ret) { - folly::runBenchmarksOnFlag(); - } - return ret; + // 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); }