Use the GTest portability headers
[folly.git] / folly / test / SingletonTest.cpp
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <thread>
18
19 #include <folly/Singleton.h>
20 #include <folly/Subprocess.h>
21 #include <folly/experimental/io/FsUtil.h>
22 #include <folly/io/async/EventBase.h>
23 #include <folly/portability/GMock.h>
24 #include <folly/portability/GTest.h>
25 #include <folly/test/SingletonTestStructs.h>
26
27 #include <glog/logging.h>
28 #include <boost/thread/barrier.hpp>
29
30 using namespace folly;
31
32 TEST(Singleton, MissingSingleton) {
33   EXPECT_DEATH([]() { auto u = Singleton<UnregisteredWatchdog>::try_get(); }(),
34       "");
35 }
36
37 struct BasicUsageTag {};
38 template <typename T, typename Tag = detail::DefaultTag>
39 using SingletonBasicUsage = Singleton <T, Tag, BasicUsageTag>;
40
41 // Exercise some basic codepaths ensuring registration order and
42 // destruction order happen as expected, that instances are created
43 // when expected, etc etc.
44 TEST(Singleton, BasicUsage) {
45   auto& vault = *SingletonVault::singleton<BasicUsageTag>();
46
47   EXPECT_EQ(vault.registeredSingletonCount(), 0);
48   SingletonBasicUsage<Watchdog> watchdog_singleton;
49   EXPECT_EQ(vault.registeredSingletonCount(), 1);
50
51   SingletonBasicUsage<ChildWatchdog> child_watchdog_singleton;
52   EXPECT_EQ(vault.registeredSingletonCount(), 2);
53
54   vault.registrationComplete();
55
56   // limit a scope to release references so we can destroy them later
57   {
58     std::shared_ptr<Watchdog> s1 = SingletonBasicUsage<Watchdog>::try_get();
59     EXPECT_NE(s1, nullptr);
60
61     std::shared_ptr<Watchdog> s2 = SingletonBasicUsage<Watchdog>::try_get();
62     EXPECT_NE(s2, nullptr);
63
64     EXPECT_EQ(s1, s2);
65     EXPECT_EQ(s1.get(), SingletonBasicUsage<Watchdog>::try_get_fast().get());
66
67     std::shared_ptr<ChildWatchdog> s3 =
68       SingletonBasicUsage<ChildWatchdog>::try_get();
69     EXPECT_NE(s3, nullptr);
70     EXPECT_NE(s2, s3);
71
72     EXPECT_EQ(vault.registeredSingletonCount(), 2);
73     EXPECT_EQ(vault.livingSingletonCount(), 2);
74   }
75
76   vault.destroyInstances();
77   EXPECT_EQ(vault.registeredSingletonCount(), 2);
78   EXPECT_EQ(vault.livingSingletonCount(), 0);
79 }
80
81 struct DirectUsageTag {};
82 template <typename T, typename Tag = detail::DefaultTag>
83 using SingletonDirectUsage = Singleton <T, Tag, DirectUsageTag>;
84
85 TEST(Singleton, DirectUsage) {
86   auto& vault = *SingletonVault::singleton<DirectUsageTag>();
87
88   EXPECT_EQ(vault.registeredSingletonCount(), 0);
89
90   // Verify we can get to the underlying singletons via directly using
91   // the singleton definition.
92   SingletonDirectUsage<Watchdog> watchdog;
93   struct TestTag {};
94   SingletonDirectUsage<Watchdog, TestTag> named_watchdog;
95   EXPECT_EQ(vault.registeredSingletonCount(), 2);
96   vault.registrationComplete();
97
98   EXPECT_NE(watchdog.try_get(), nullptr);
99   EXPECT_EQ(watchdog.try_get(), SingletonDirectUsage<Watchdog>::try_get());
100   EXPECT_NE(watchdog.try_get(), named_watchdog.try_get());
101   EXPECT_EQ(watchdog.try_get()->livingWatchdogCount(), 2);
102
103   vault.destroyInstances();
104 }
105
106 struct NamedUsageTag {};
107 template <typename T, typename Tag = detail::DefaultTag>
108 using SingletonNamedUsage = Singleton <T, Tag, NamedUsageTag>;
109
110 TEST(Singleton, NamedUsage) {
111   auto& vault = *SingletonVault::singleton<NamedUsageTag>();
112
113   EXPECT_EQ(vault.registeredSingletonCount(), 0);
114
115   // Define two named Watchdog singletons and one unnamed singleton.
116   struct Watchdog1 {};
117   struct Watchdog2 {};
118   typedef detail::DefaultTag Watchdog3;
119   SingletonNamedUsage<Watchdog, Watchdog1> watchdog1_singleton;
120   EXPECT_EQ(vault.registeredSingletonCount(), 1);
121   SingletonNamedUsage<Watchdog, Watchdog2> watchdog2_singleton;
122   EXPECT_EQ(vault.registeredSingletonCount(), 2);
123   SingletonNamedUsage<Watchdog, Watchdog3> watchdog3_singleton;
124   EXPECT_EQ(vault.registeredSingletonCount(), 3);
125
126   vault.registrationComplete();
127   {
128     // Verify our three singletons are distinct and non-nullptr.
129     auto s1 = SingletonNamedUsage<Watchdog, Watchdog1>::try_get();
130     EXPECT_EQ(s1, watchdog1_singleton.try_get());
131     auto s2 = SingletonNamedUsage<Watchdog, Watchdog2>::try_get();
132     EXPECT_EQ(s2, watchdog2_singleton.try_get());
133     EXPECT_NE(s1, s2);
134     auto s3 = SingletonNamedUsage<Watchdog, Watchdog3>::try_get();
135     EXPECT_EQ(s3, watchdog3_singleton.try_get());
136     EXPECT_NE(s3, s1);
137     EXPECT_NE(s3, s2);
138
139     // Verify the "default" singleton is the same as the DefaultTag-tagged
140     // singleton.
141     auto s4 = SingletonNamedUsage<Watchdog>::try_get();
142     EXPECT_EQ(s4, watchdog3_singleton.try_get());
143   }
144
145   vault.destroyInstances();
146 }
147
148 struct NaughtyUsageTag {};
149 template <typename T, typename Tag = detail::DefaultTag>
150 using SingletonNaughtyUsage = Singleton <T, Tag, NaughtyUsageTag>;
151 struct NaughtyUsageTag2 {};
152 template <typename T, typename Tag = detail::DefaultTag>
153 using SingletonNaughtyUsage2 = Singleton <T, Tag, NaughtyUsageTag2>;
154
155 // Some pathological cases such as getting unregistered singletons,
156 // double registration, etc.
157 TEST(Singleton, NaughtyUsage) {
158   auto& vault = *SingletonVault::singleton<NaughtyUsageTag>();
159
160   vault.registrationComplete();
161
162   // Unregistered.
163   EXPECT_DEATH(Singleton<Watchdog>::try_get(), "");
164   EXPECT_DEATH(SingletonNaughtyUsage<Watchdog>::try_get(), "");
165
166   vault.destroyInstances();
167
168   auto& vault2 = *SingletonVault::singleton<NaughtyUsageTag2>();
169
170    EXPECT_DEATH(SingletonNaughtyUsage2<Watchdog>::try_get(), "");
171   SingletonNaughtyUsage2<Watchdog> watchdog_singleton;
172
173   // double registration
174   EXPECT_DEATH([]() { SingletonNaughtyUsage2<Watchdog> watchdog_singleton; }(),
175                "");
176   vault2.destroyInstances();
177
178   // double registration after destroy
179   EXPECT_DEATH([]() { SingletonNaughtyUsage2<Watchdog> watchdog_singleton; }(),
180                "");
181 }
182
183 struct SharedPtrUsageTag {};
184 template <typename T, typename Tag = detail::DefaultTag>
185 using SingletonSharedPtrUsage = Singleton <T, Tag, SharedPtrUsageTag>;
186
187 // TODO (anob): revisit this test
188 TEST(Singleton, SharedPtrUsage) {
189   struct WatchdogHolder {
190     ~WatchdogHolder() {
191       if (watchdog) {
192         LOG(ERROR) << "The following log message with stack trace is expected";
193       }
194     }
195
196     std::shared_ptr<Watchdog> watchdog;
197   };
198
199   auto& vault = *SingletonVault::singleton<SharedPtrUsageTag>();
200
201   EXPECT_EQ(vault.registeredSingletonCount(), 0);
202   SingletonSharedPtrUsage<Watchdog> watchdog_singleton;
203   EXPECT_EQ(vault.registeredSingletonCount(), 1);
204
205   SingletonSharedPtrUsage<ChildWatchdog> child_watchdog_singleton;
206   EXPECT_EQ(vault.registeredSingletonCount(), 2);
207
208   struct ATag {};
209   SingletonSharedPtrUsage<Watchdog, ATag> named_watchdog_singleton;
210
211   SingletonSharedPtrUsage<WatchdogHolder> watchdog_holder_singleton;
212
213   vault.registrationComplete();
214
215   // Initilize holder singleton first, so that it's the last one to be
216   // destroyed.
217   watchdog_holder_singleton.try_get();
218
219   auto s1 = SingletonSharedPtrUsage<Watchdog>::try_get().get();
220   EXPECT_NE(s1, nullptr);
221
222   auto s2 = SingletonSharedPtrUsage<Watchdog>::try_get().get();
223   EXPECT_NE(s2, nullptr);
224
225   EXPECT_EQ(s1, s2);
226
227   auto weak_s1 = SingletonSharedPtrUsage<Watchdog>::get_weak();
228
229   auto shared_s1 = weak_s1.lock();
230   EXPECT_EQ(shared_s1.get(), s1);
231   EXPECT_EQ(shared_s1.use_count(), 2);
232
233   auto old_serial = shared_s1->serial_number;
234
235   {
236     auto named_weak_s1 =
237       SingletonSharedPtrUsage<Watchdog, ATag>::get_weak();
238     auto locked = named_weak_s1.lock();
239     EXPECT_NE(locked.get(), shared_s1.get());
240   }
241
242   // We should release externally locked shared_ptr, otherwise it will be
243   // considered a leak
244   watchdog_holder_singleton.try_get()->watchdog = std::move(shared_s1);
245
246   LOG(ERROR) << "The following log message regarding shared_ptr is expected";
247   {
248     auto start_time = std::chrono::steady_clock::now();
249     vault.destroyInstances();
250     auto duration = std::chrono::steady_clock::now() - start_time;
251     EXPECT_TRUE(duration > std::chrono::seconds{4} &&
252                 duration < std::chrono::seconds{6});
253   }
254   EXPECT_EQ(vault.registeredSingletonCount(), 4);
255   EXPECT_EQ(vault.livingSingletonCount(), 0);
256
257   EXPECT_TRUE(weak_s1.expired());
258
259   auto empty_s1 = SingletonSharedPtrUsage<Watchdog>::get_weak();
260   EXPECT_FALSE(empty_s1.lock());
261
262   vault.reenableInstances();
263
264   {
265     // Singleton should be re-created only after reenableInstances() was called.
266     auto new_s1 = SingletonSharedPtrUsage<Watchdog>::try_get();
267     // Track serial number rather than pointer since the memory could be
268     // re-used when we create new_s1.
269     EXPECT_NE(new_s1->serial_number, old_serial);
270   }
271
272   auto new_s1_weak = SingletonSharedPtrUsage<Watchdog>::get_weak();
273   auto new_s1_shared = new_s1_weak.lock();
274   std::thread t([new_s1_shared]() mutable {
275       std::this_thread::sleep_for(std::chrono::seconds{2});
276       new_s1_shared.reset();
277     });
278   new_s1_shared.reset();
279   {
280     auto start_time = std::chrono::steady_clock::now();
281     vault.destroyInstances();
282     auto duration = std::chrono::steady_clock::now() - start_time;
283     EXPECT_TRUE(duration > std::chrono::seconds{1} &&
284                 duration < std::chrono::seconds{3});
285   }
286   EXPECT_TRUE(new_s1_weak.expired());
287   t.join();
288 }
289
290 // Some classes to test singleton dependencies.  NeedySingleton has a
291 // dependency on NeededSingleton, which happens during its
292 // construction.
293 struct NeedyTag {};
294 template <typename T, typename Tag = detail::DefaultTag>
295 using SingletonNeedy = Singleton <T, Tag, NeedyTag>;
296
297 struct NeededSingleton {};
298 struct NeedySingleton {
299   NeedySingleton() {
300     auto unused = SingletonNeedy<NeededSingleton>::try_get();
301     EXPECT_NE(unused, nullptr);
302   }
303 };
304
305 // Ensure circular dependencies fail -- a singleton that needs itself, whoops.
306 struct SelfNeedyTag {};
307 template <typename T, typename Tag = detail::DefaultTag>
308 using SingletonSelfNeedy = Singleton <T, Tag, SelfNeedyTag>;
309
310 struct SelfNeedySingleton {
311   SelfNeedySingleton() {
312     auto unused = SingletonSelfNeedy<SelfNeedySingleton>::try_get();
313     EXPECT_NE(unused, nullptr);
314   }
315 };
316
317 TEST(Singleton, SingletonDependencies) {
318   SingletonNeedy<NeededSingleton> needed_singleton;
319   SingletonNeedy<NeedySingleton> needy_singleton;
320   auto& needy_vault = *SingletonVault::singleton<NeedyTag>();
321
322   needy_vault.registrationComplete();
323
324   EXPECT_EQ(needy_vault.registeredSingletonCount(), 2);
325   EXPECT_EQ(needy_vault.livingSingletonCount(), 0);
326
327   auto needy = SingletonNeedy<NeedySingleton>::try_get();
328   EXPECT_EQ(needy_vault.livingSingletonCount(), 2);
329
330   SingletonSelfNeedy<SelfNeedySingleton> self_needy_singleton;
331   auto& self_needy_vault = *SingletonVault::singleton<SelfNeedyTag>();
332
333   self_needy_vault.registrationComplete();
334   EXPECT_DEATH([]() { SingletonSelfNeedy<SelfNeedySingleton>::try_get(); }(),
335       "");
336 }
337
338 // A test to ensure multiple threads contending on singleton creation
339 // properly wait for creation rather than thinking it is a circular
340 // dependency.
341 class Slowpoke : public Watchdog {
342  public:
343   Slowpoke() { std::this_thread::sleep_for(std::chrono::milliseconds(10)); }
344 };
345
346 struct ConcurrencyTag {};
347 template <typename T, typename Tag = detail::DefaultTag>
348 using SingletonConcurrency = Singleton <T, Tag, ConcurrencyTag>;
349
350 TEST(Singleton, SingletonConcurrency) {
351   auto& vault = *SingletonVault::singleton<ConcurrencyTag>();
352   SingletonConcurrency<Slowpoke> slowpoke_singleton;
353   vault.registrationComplete();
354
355   std::mutex gatekeeper;
356   gatekeeper.lock();
357   auto func = [&gatekeeper]() {
358     gatekeeper.lock();
359     gatekeeper.unlock();
360     auto unused = SingletonConcurrency<Slowpoke>::try_get();
361   };
362
363   EXPECT_EQ(vault.livingSingletonCount(), 0);
364   std::vector<std::thread> threads;
365   for (int i = 0; i < 100; ++i) {
366     threads.emplace_back(func);
367   }
368   // If circular dependency checks fail, the unlock would trigger a
369   // crash.  Instead, it succeeds, and we have exactly one living
370   // singleton.
371   gatekeeper.unlock();
372   for (auto& t : threads) {
373     t.join();
374   }
375   EXPECT_EQ(vault.livingSingletonCount(), 1);
376 }
377
378 struct ErrorConstructor {
379   static size_t constructCount_;
380   ErrorConstructor() {
381     if ((constructCount_++) == 0) {
382       throw std::runtime_error("first time fails");
383     }
384   }
385 };
386 size_t ErrorConstructor::constructCount_(0);
387
388 struct CreationErrorTag {};
389 template <typename T, typename Tag = detail::DefaultTag>
390 using SingletonCreationError = Singleton<T, Tag, CreationErrorTag>;
391
392 TEST(Singleton, SingletonCreationError) {
393   SingletonVault::singleton<CreationErrorTag>();
394   SingletonCreationError<ErrorConstructor> error_once_singleton;
395
396   // first time should error out
397   EXPECT_THROW(error_once_singleton.try_get(), std::runtime_error);
398
399   // second time it'll work fine
400   error_once_singleton.try_get();
401   SUCCEED();
402 }
403
404 struct ConcurrencyStressTag {};
405 template <typename T, typename Tag = detail::DefaultTag>
406 using SingletonConcurrencyStress = Singleton <T, Tag, ConcurrencyStressTag>;
407
408 TEST(Singleton, SingletonConcurrencyStress) {
409   auto& vault = *SingletonVault::singleton<ConcurrencyStressTag>();
410   SingletonConcurrencyStress<Slowpoke> slowpoke_singleton;
411
412   std::vector<std::thread> ts;
413   for (size_t i = 0; i < 100; ++i) {
414     ts.emplace_back([&]() {
415         slowpoke_singleton.try_get();
416       });
417   }
418
419   for (size_t i = 0; i < 100; ++i) {
420     std::chrono::milliseconds d(20);
421
422     std::this_thread::sleep_for(d);
423     vault.destroyInstances();
424     std::this_thread::sleep_for(d);
425     vault.destroyInstances();
426   }
427
428   for (auto& t : ts) {
429     t.join();
430   }
431 }
432
433 namespace {
434 struct EagerInitSyncTag {};
435 }
436 template <typename T, typename Tag = detail::DefaultTag>
437 using SingletonEagerInitSync = Singleton<T, Tag, EagerInitSyncTag>;
438 TEST(Singleton, SingletonEagerInitSync) {
439   auto& vault = *SingletonVault::singleton<EagerInitSyncTag>();
440   bool didEagerInit = false;
441   auto sing = SingletonEagerInitSync<std::string>(
442                   [&] {didEagerInit = true; return new std::string("foo"); })
443               .shouldEagerInit();
444   vault.registrationComplete();
445   EXPECT_FALSE(didEagerInit);
446   vault.doEagerInit();
447   EXPECT_TRUE(didEagerInit);
448   sing.get_weak();  // (avoid compile error complaining about unused var 'sing')
449 }
450
451 namespace {
452 struct EagerInitAsyncTag {};
453 }
454 template <typename T, typename Tag = detail::DefaultTag>
455 using SingletonEagerInitAsync = Singleton<T, Tag, EagerInitAsyncTag>;
456 TEST(Singleton, SingletonEagerInitAsync) {
457   auto& vault = *SingletonVault::singleton<EagerInitAsyncTag>();
458   bool didEagerInit = false;
459   auto sing = SingletonEagerInitAsync<std::string>(
460                   [&] {didEagerInit = true; return new std::string("foo"); })
461               .shouldEagerInit();
462   folly::EventBase eb;
463   folly::Baton<> done;
464   vault.registrationComplete();
465   EXPECT_FALSE(didEagerInit);
466   vault.doEagerInitVia(eb, &done);
467   eb.loop();
468   done.wait();
469   EXPECT_TRUE(didEagerInit);
470   sing.get_weak();  // (avoid compile error complaining about unused var 'sing')
471 }
472
473 namespace {
474 class TestEagerInitParallelExecutor : public folly::Executor {
475  public:
476   explicit TestEagerInitParallelExecutor(const size_t threadCount) {
477     eventBases_.reserve(threadCount);
478     threads_.reserve(threadCount);
479     for (size_t i = 0; i < threadCount; i++) {
480       eventBases_.push_back(std::make_shared<folly::EventBase>());
481       auto eb = eventBases_.back();
482       threads_.emplace_back(std::make_shared<std::thread>(
483           [eb] { eb->loopForever(); }));
484     }
485   }
486
487   virtual ~TestEagerInitParallelExecutor() override {
488     for (auto eb : eventBases_) {
489       eb->runInEventBaseThread([eb] { eb->terminateLoopSoon(); });
490     }
491     for (auto thread : threads_) {
492       thread->join();
493     }
494   }
495
496   virtual void add(folly::Func func) override {
497     const auto index = (counter_ ++) % eventBases_.size();
498     eventBases_[index]->add(std::move(func));
499   }
500
501  private:
502   std::vector<std::shared_ptr<folly::EventBase>> eventBases_;
503   std::vector<std::shared_ptr<std::thread>> threads_;
504   std::atomic<size_t> counter_ {0};
505 };
506 }  // namespace
507
508 namespace {
509 struct EagerInitParallelTag {};
510 }
511 template <typename T, typename Tag = detail::DefaultTag>
512 using SingletonEagerInitParallel = Singleton<T, Tag, EagerInitParallelTag>;
513 TEST(Singleton, SingletonEagerInitParallel) {
514   const static size_t kIters = 1000;
515   const static size_t kThreads = 20;
516
517   std::atomic<size_t> initCounter;
518
519   auto& vault = *SingletonVault::singleton<EagerInitParallelTag>();
520
521   auto sing = SingletonEagerInitParallel<std::string>(
522                   [&] {++initCounter; return new std::string(""); })
523               .shouldEagerInit();
524
525   for (size_t i = 0; i < kIters; i++) {
526     SCOPE_EXIT {
527       // clean up each time
528       vault.destroyInstances();
529       vault.reenableInstances();
530     };
531
532     initCounter.store(0);
533
534     {
535       std::vector<std::shared_ptr<std::thread>> threads;
536       boost::barrier barrier(kThreads);
537       TestEagerInitParallelExecutor exe(kThreads);
538       vault.registrationComplete();
539
540       EXPECT_EQ(0, initCounter.load());
541
542       for (size_t j = 0; j < kThreads; j++) {
543         threads.push_back(std::make_shared<std::thread>([&] {
544           barrier.wait();
545           vault.doEagerInitVia(exe);
546         }));
547       }
548
549       for (auto thread : threads) {
550         thread->join();
551       }
552     }
553
554     EXPECT_EQ(1, initCounter.load());
555
556     sing.get_weak();  // (avoid compile error complaining about unused var)
557   }
558 }
559
560 struct MockTag {};
561 template <typename T, typename Tag = detail::DefaultTag>
562 using SingletonMock = Singleton <T, Tag, MockTag>;
563
564 // Verify that existing Singleton's can be overridden
565 // using the make_mock functionality.
566 TEST(Singleton, MockTest) {
567   auto& vault = *SingletonVault::singleton<MockTag>();
568
569   SingletonMock<Watchdog> watchdog_singleton;
570   vault.registrationComplete();
571
572   // Registring singletons after registrationComplete called works
573   // with make_mock (but not with Singleton ctor).
574   EXPECT_EQ(vault.registeredSingletonCount(), 1);
575   int serial_count_first = SingletonMock<Watchdog>::try_get()->serial_number;
576
577   // Override existing mock using make_mock.
578   SingletonMock<Watchdog>::make_mock();
579
580   EXPECT_EQ(vault.registeredSingletonCount(), 1);
581   int serial_count_mock = SingletonMock<Watchdog>::try_get()->serial_number;
582
583   // If serial_count value is the same, then singleton was not replaced.
584   EXPECT_NE(serial_count_first, serial_count_mock);
585
586   // Override existing mock using make_mock one more time
587   SingletonMock<Watchdog>::make_mock();
588
589   EXPECT_EQ(vault.registeredSingletonCount(), 1);
590   int serial_count_mock2 = SingletonMock<Watchdog>::try_get()->serial_number;
591
592   // If serial_count value is the same, then singleton was not replaced.
593   EXPECT_NE(serial_count_first, serial_count_mock2);
594   EXPECT_NE(serial_count_mock, serial_count_mock2);
595
596   vault.destroyInstances();
597 }
598
599 TEST(Singleton, DoubleRegistrationLogging) {
600   const auto basename = "singleton_double_registration";
601   const auto sub = fs::executable_path().remove_filename() / basename;
602   auto p = Subprocess(
603       std::vector<std::string>{sub.string()},
604       Subprocess::Options()
605           .stdin(Subprocess::CLOSE)
606           .stdout(Subprocess::CLOSE)
607           .pipeStderr()
608           .closeOtherFds());
609   auto err = p.communicate("").second;
610   auto res = p.wait();
611   EXPECT_EQ(ProcessReturnCode::KILLED, res.state());
612   EXPECT_EQ(SIGABRT, res.killSignal());
613   EXPECT_THAT(err, testing::StartsWith("Double registration of singletons"));
614 }