get_fast/get_weak_fast API for folly::Singleton
[folly.git] / folly / experimental / test / SingletonTest.cpp
index f0815d912eca5d4009e6ca21aaf6f9f7d48e565d..9e19574718ae19173b4ce3a0d736363821bb1aef 100644 (file)
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <thread>
+
 #include <folly/experimental/Singleton.h>
 
 #include <folly/Benchmark.h>
@@ -41,6 +43,7 @@ struct Watchdog {
   }
 
   const size_t serial_number;
+  size_t livingWatchdogCount() const { return creation_order.size(); }
 
   Watchdog(const Watchdog&) = delete;
   Watchdog& operator=(const Watchdog&) = delete;
@@ -115,10 +118,63 @@ TEST(Singleton, BasicUsage) {
   EXPECT_EQ(vault.livingSingletonCount(), 0);
 }
 
+TEST(Singleton, DirectUsage) {
+  SingletonVault vault;
+
+  EXPECT_EQ(vault.registeredSingletonCount(), 0);
+
+  // Verify we can get to the underlying singletons via directly using
+  // the singleton definition.
+  Singleton<Watchdog> watchdog(nullptr, nullptr, &vault);
+  Singleton<Watchdog> named_watchdog("named", nullptr, nullptr, &vault);
+  EXPECT_EQ(vault.registeredSingletonCount(), 2);
+  vault.registrationComplete();
+
+  EXPECT_NE(watchdog.ptr(), nullptr);
+  EXPECT_EQ(watchdog.ptr(), Singleton<Watchdog>::get(&vault));
+  EXPECT_NE(watchdog.ptr(), named_watchdog.ptr());
+  EXPECT_EQ(watchdog->livingWatchdogCount(), 2);
+  EXPECT_EQ((*watchdog).livingWatchdogCount(), 2);
+}
+
+TEST(Singleton, NamedUsage) {
+  SingletonVault vault;
+
+  EXPECT_EQ(vault.registeredSingletonCount(), 0);
+
+  // Define two named Watchdog singletons and one unnamed singleton.
+  Singleton<Watchdog> watchdog1_singleton(
+      "watchdog1", nullptr, nullptr, &vault);
+  EXPECT_EQ(vault.registeredSingletonCount(), 1);
+  Singleton<Watchdog> watchdog2_singleton(
+      "watchdog2", nullptr, nullptr, &vault);
+  EXPECT_EQ(vault.registeredSingletonCount(), 2);
+  Singleton<Watchdog> watchdog3_singleton(nullptr, nullptr, &vault);
+  EXPECT_EQ(vault.registeredSingletonCount(), 3);
+
+  vault.registrationComplete();
+
+  // Verify our three singletons are distinct and non-nullptr.
+  Watchdog* s1 = Singleton<Watchdog>::get("watchdog1", &vault);
+  EXPECT_EQ(s1, watchdog1_singleton.ptr());
+  Watchdog* s2 = Singleton<Watchdog>::get("watchdog2", &vault);
+  EXPECT_EQ(s2, watchdog2_singleton.ptr());
+  EXPECT_NE(s1, s2);
+  Watchdog* s3 = Singleton<Watchdog>::get(&vault);
+  EXPECT_EQ(s3, watchdog3_singleton.ptr());
+  EXPECT_NE(s3, s1);
+  EXPECT_NE(s3, s2);
+
+  // Verify the "default" singleton is the same as the empty string
+  // singleton.
+  Watchdog* s4 = Singleton<Watchdog>::get("", &vault);
+  EXPECT_EQ(s4, watchdog3_singleton.ptr());
+}
+
 // Some pathological cases such as getting unregistered singletons,
 // double registration, etc.
 TEST(Singleton, NaughtyUsage) {
-  SingletonVault vault;
+  SingletonVault vault(SingletonVault::Type::Strict);
   vault.registrationComplete();
 
   // Unregistered.
@@ -132,10 +188,7 @@ TEST(Singleton, NaughtyUsage) {
                }(),
                std::logic_error);
 
-  EXPECT_THROW([]() { Singleton<Watchdog> watchdog_singleton; }(),
-               std::logic_error);
-
-  SingletonVault vault_2;
+  SingletonVault vault_2(SingletonVault::Type::Strict);
   EXPECT_THROW(Singleton<Watchdog>::get(&vault_2), std::logic_error);
   Singleton<Watchdog> watchdog_singleton(nullptr, nullptr, &vault_2);
   // double registration
@@ -163,6 +216,8 @@ TEST(Singleton, SharedPtrUsage) {
   Singleton<ChildWatchdog> child_watchdog_singleton(nullptr, nullptr, &vault);
   EXPECT_EQ(vault.registeredSingletonCount(), 2);
 
+  Singleton<Watchdog> named_watchdog_singleton(
+      "a_name", nullptr, nullptr, &vault);
   vault.registrationComplete();
 
   Watchdog* s1 = Singleton<Watchdog>::get(&vault);
@@ -178,9 +233,15 @@ TEST(Singleton, SharedPtrUsage) {
   EXPECT_EQ(shared_s1.get(), s1);
   EXPECT_EQ(shared_s1.use_count(), 2);
 
+  {
+    auto named_weak_s1 = Singleton<Watchdog>::get_weak("a_name", &vault);
+    auto locked = named_weak_s1.lock();
+    EXPECT_NE(locked.get(), shared_s1.get());
+  }
+
   LOG(ERROR) << "The following log message regarding ref counts is expected";
   vault.destroyInstances();
-  EXPECT_EQ(vault.registeredSingletonCount(), 2);
+  EXPECT_EQ(vault.registeredSingletonCount(), 3);
   EXPECT_EQ(vault.livingSingletonCount(), 0);
 
   EXPECT_EQ(shared_s1.use_count(), 1);
@@ -199,6 +260,12 @@ TEST(Singleton, SharedPtrUsage) {
   locked_s1 = weak_s1.lock();
   EXPECT_TRUE(weak_s1.expired());
 
+  auto empty_s1 = Singleton<Watchdog>::get_weak(&vault);
+  EXPECT_FALSE(empty_s1.lock());
+
+  vault.reenableInstances();
+
+  // Singleton should be re-created only after reenableInstances() was called.
   Watchdog* new_s1 = Singleton<Watchdog>::get(&vault);
   EXPECT_NE(new_s1->serial_number, old_serial);
 }
@@ -245,6 +312,67 @@ TEST(Singleton, SingletonDependencies) {
                std::out_of_range);
 }
 
+// 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)); }
+};
+
+TEST(Singleton, SingletonConcurrency) {
+  SingletonVault vault;
+  Singleton<Slowpoke> slowpoke_singleton(nullptr, nullptr, &vault);
+  vault.registrationComplete();
+
+  std::mutex gatekeeper;
+  gatekeeper.lock();
+  auto func = [&vault, &gatekeeper]() {
+    gatekeeper.lock();
+    gatekeeper.unlock();
+    auto unused = Singleton<Slowpoke>::get(&vault);
+  };
+
+  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);
+}
+
+TEST(Singleton, SingletonConcurrencyStress) {
+  SingletonVault vault;
+  Singleton<Slowpoke> slowpoke_singleton(nullptr, nullptr, &vault);
+
+  std::vector<std::thread> ts;
+  for (size_t i = 0; i < 100; ++i) {
+    ts.emplace_back([&]() {
+        slowpoke_singleton.get_weak(&vault).lock();
+      });
+  }
+
+  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();
+  }
+}
+
 // Benchmarking a normal singleton vs a Meyers singleton vs a Folly
 // singleton.  Meyers are insanely fast, but (hopefully) Folly
 // singletons are fast "enough."
@@ -259,33 +387,77 @@ int* getNormalSingleton() {
   return &normal_singleton_value;
 }
 
+// Verify that existing Singleton's can be overridden
+// using the make_mock functionality.
+TEST(Singleton, make_mock) {
+  SingletonVault vault(SingletonVault::Type::Strict);
+  Singleton<Watchdog> watchdog_singleton(nullptr, nullptr, &vault);
+  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 = Singleton<Watchdog>::get(&vault)->serial_number;
+
+  // Override existing mock using make_mock.
+  Singleton<Watchdog>::make_mock(nullptr, nullptr, &vault);
+
+  EXPECT_EQ(vault.registeredSingletonCount(), 1);
+  int serial_count_mock = Singleton<Watchdog>::get(&vault)->serial_number;
+
+  // 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;
 };
 
 BENCHMARK(NormalSingleton, n) {
-  for (int i = 0; i < n; ++i) {
+  for (size_t i = 0; i < n; ++i) {
     doNotOptimizeAway(getNormalSingleton());
   }
 }
 
 BENCHMARK_RELATIVE(MeyersSingleton, n) {
-  for (int i = 0; i < n; ++i) {
+  for (size_t i = 0; i < n; ++i) {
     doNotOptimizeAway(getMeyersSingleton());
   }
 }
 
-BENCHMARK_RELATIVE(FollySingleton, n) {
+BENCHMARK_RELATIVE(FollySingletonSlow, n) {
   SingletonVault benchmark_vault;
   Singleton<BenchmarkSingleton> benchmark_singleton(
       nullptr, nullptr, &benchmark_vault);
   benchmark_vault.registrationComplete();
 
-  for (int i = 0; i < n; ++i) {
+  for (size_t i = 0; i < n; ++i) {
     doNotOptimizeAway(Singleton<BenchmarkSingleton>::get(&benchmark_vault));
   }
 }
 
+BENCHMARK_RELATIVE(FollySingletonFast, n) {
+  SingletonVault benchmark_vault;
+  Singleton<BenchmarkSingleton> benchmark_singleton(
+      nullptr, nullptr, &benchmark_vault);
+  benchmark_vault.registrationComplete();
+
+  for (size_t i = 0; i < n; ++i) {
+    doNotOptimizeAway(benchmark_singleton.get_fast());
+  }
+}
+
+BENCHMARK_RELATIVE(FollySingletonFastWeak, n) {
+  SingletonVault benchmark_vault;
+  Singleton<BenchmarkSingleton> benchmark_singleton(
+      nullptr, nullptr, &benchmark_vault);
+  benchmark_vault.registrationComplete();
+
+  for (size_t i = 0; i < n; ++i) {
+    benchmark_singleton.get_weak_fast();
+  }
+}
+
 int main(int argc, char* argv[]) {
   testing::InitGoogleTest(&argc, argv);
   google::InitGoogleLogging(argv[0]);