48951a621ff7a922686435da671d6ded7a0f72cd
[folly.git] / folly / test / SynchronizedTestLib-inl.h
1 /*
2  * Copyright 2017 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 #pragma once
18
19 #include <folly/Foreach.h>
20 #include <folly/Random.h>
21 #include <folly/Synchronized.h>
22 #include <folly/portability/GTest.h>
23 #include <glog/logging.h>
24 #include <algorithm>
25 #include <condition_variable>
26 #include <functional>
27 #include <map>
28 #include <random>
29 #include <thread>
30 #include <vector>
31
32 namespace folly {
33 namespace sync_tests {
34
35 inline std::mt19937& getRNG() {
36   static const auto seed = folly::randomNumberSeed();
37   static std::mt19937 rng(seed);
38   return rng;
39 }
40
41 void randomSleep(std::chrono::milliseconds min, std::chrono::milliseconds max) {
42   std::uniform_int_distribution<> range(min.count(), max.count());
43   std::chrono::milliseconds duration(range(getRNG()));
44   /* sleep override */
45   std::this_thread::sleep_for(duration);
46 }
47
48 /*
49  * Run a functon simultaneously in a number of different threads.
50  *
51  * The function will be passed the index number of the thread it is running in.
52  * This function makes an attempt to synchronize the start of the threads as
53  * best as possible.  It waits for all threads to be allocated and started
54  * before invoking the function.
55  */
56 template <class Function>
57 void runParallel(size_t numThreads, const Function& function) {
58   std::vector<std::thread> threads;
59   threads.reserve(numThreads);
60
61   // Variables used to synchronize all threads to try and start them
62   // as close to the same time as possible
63   folly::Synchronized<size_t, std::mutex> threadsReady(0);
64   std::condition_variable readyCV;
65   folly::Synchronized<bool, std::mutex> go(false);
66   std::condition_variable goCV;
67
68   auto worker = [&](size_t threadIndex) {
69     // Signal that we are ready
70     ++(*threadsReady.lock());
71     readyCV.notify_one();
72
73     // Wait until we are given the signal to start
74     // The purpose of this is to try and make sure all threads start
75     // as close to the same time as possible.
76     {
77       auto lockedGo = go.lock();
78       goCV.wait(lockedGo.getUniqueLock(), [&] { return *lockedGo; });
79     }
80
81     function(threadIndex);
82   };
83
84   // Start all of the threads
85   for (size_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) {
86     threads.emplace_back([threadIndex, &worker]() { worker(threadIndex); });
87   }
88
89   // Wait for all threads to become ready
90   {
91     auto readyLocked = threadsReady.lock();
92     readyCV.wait(readyLocked.getUniqueLock(), [&] {
93       return *readyLocked == numThreads;
94     });
95   }
96   // Now signal the threads that they can go
97   go = true;
98   goCV.notify_all();
99
100   // Wait for all threads to finish
101   for (auto& thread : threads) {
102     thread.join();
103   }
104 }
105
106 // testBasic() version for shared lock types
107 template <class Mutex>
108 typename std::enable_if<folly::LockTraits<Mutex>::is_shared>::type
109 testBasicImpl() {
110   folly::Synchronized<std::vector<int>, Mutex> obj;
111   const auto& constObj = obj;
112
113   obj.wlock()->resize(1000);
114
115   folly::Synchronized<std::vector<int>, Mutex> obj2{*obj.wlock()};
116   EXPECT_EQ(1000, obj2.rlock()->size());
117
118   {
119     auto lockedObj = obj.wlock();
120     lockedObj->push_back(10);
121     EXPECT_EQ(1001, lockedObj->size());
122     EXPECT_EQ(10, lockedObj->back());
123     EXPECT_EQ(1000, obj2.wlock()->size());
124     EXPECT_EQ(1000, obj2.rlock()->size());
125
126     {
127       auto unlocker = lockedObj.scopedUnlock();
128       EXPECT_EQ(1001, obj.wlock()->size());
129     }
130   }
131
132   {
133     auto lockedObj = obj.rlock();
134     EXPECT_EQ(1001, lockedObj->size());
135     EXPECT_EQ(1001, obj.rlock()->size());
136     {
137       auto unlocker = lockedObj.scopedUnlock();
138       EXPECT_EQ(1001, obj.wlock()->size());
139     }
140   }
141
142   obj.wlock()->front() = 2;
143
144   {
145     // contextualLock() on a const reference should grab a shared lock
146     auto lockedObj = constObj.contextualLock();
147     EXPECT_EQ(2, lockedObj->front());
148     EXPECT_EQ(2, constObj.rlock()->front());
149     EXPECT_EQ(2, obj.rlock()->front());
150   }
151
152   EXPECT_EQ(1001, obj.rlock()->size());
153   EXPECT_EQ(2, obj.rlock()->front());
154   EXPECT_EQ(10, obj.rlock()->back());
155   EXPECT_EQ(1000, obj2.rlock()->size());
156 }
157
158 // testBasic() version for non-shared lock types
159 template <class Mutex>
160 typename std::enable_if<!folly::LockTraits<Mutex>::is_shared>::type
161 testBasicImpl() {
162   folly::Synchronized<std::vector<int>, Mutex> obj;
163   const auto& constObj = obj;
164
165   obj.lock()->resize(1000);
166
167   folly::Synchronized<std::vector<int>, Mutex> obj2{*obj.lock()};
168   EXPECT_EQ(1000, obj2.lock()->size());
169
170   {
171     auto lockedObj = obj.lock();
172     lockedObj->push_back(10);
173     EXPECT_EQ(1001, lockedObj->size());
174     EXPECT_EQ(10, lockedObj->back());
175     EXPECT_EQ(1000, obj2.lock()->size());
176
177     {
178       auto unlocker = lockedObj.scopedUnlock();
179       EXPECT_EQ(1001, obj.lock()->size());
180     }
181   }
182   {
183     auto lockedObj = constObj.lock();
184     EXPECT_EQ(1001, lockedObj->size());
185     EXPECT_EQ(10, lockedObj->back());
186     EXPECT_EQ(1000, obj2.lock()->size());
187   }
188
189   obj.lock()->front() = 2;
190
191   EXPECT_EQ(1001, obj.lock()->size());
192   EXPECT_EQ(2, obj.lock()->front());
193   EXPECT_EQ(2, obj.contextualLock()->front());
194   EXPECT_EQ(10, obj.lock()->back());
195   EXPECT_EQ(1000, obj2.lock()->size());
196 }
197
198 template <class Mutex>
199 void testBasic() {
200   testBasicImpl<Mutex>();
201 }
202
203 // testWithLock() version for shared lock types
204 template <class Mutex>
205 typename std::enable_if<folly::LockTraits<Mutex>::is_shared>::type
206 testWithLock() {
207   folly::Synchronized<std::vector<int>, Mutex> obj;
208   const auto& constObj = obj;
209
210   // Test withWLock() and withRLock()
211   obj.withWLock([](std::vector<int>& lockedObj) {
212     lockedObj.resize(1000);
213     lockedObj.push_back(10);
214     lockedObj.push_back(11);
215   });
216   obj.withWLock([](const std::vector<int>& lockedObj) {
217     EXPECT_EQ(1002, lockedObj.size());
218   });
219   constObj.withWLock([](const std::vector<int>& lockedObj) {
220     EXPECT_EQ(1002, lockedObj.size());
221     EXPECT_EQ(11, lockedObj.back());
222   });
223   obj.withRLock([](const std::vector<int>& lockedObj) {
224     EXPECT_EQ(1002, lockedObj.size());
225     EXPECT_EQ(11, lockedObj.back());
226   });
227   constObj.withRLock([](const std::vector<int>& lockedObj) {
228     EXPECT_EQ(1002, lockedObj.size());
229   });
230
231 #if __cpp_generic_lambdas >= 201304
232   obj.withWLock([](auto& lockedObj) { lockedObj.push_back(12); });
233   obj.withWLock(
234       [](const auto& lockedObj) { EXPECT_EQ(1003, lockedObj.size()); });
235   constObj.withWLock([](const auto& lockedObj) {
236     EXPECT_EQ(1003, lockedObj.size());
237     EXPECT_EQ(12, lockedObj.back());
238   });
239   obj.withRLock([](const auto& lockedObj) {
240     EXPECT_EQ(1003, lockedObj.size());
241     EXPECT_EQ(12, lockedObj.back());
242   });
243   constObj.withRLock(
244       [](const auto& lockedObj) { EXPECT_EQ(1003, lockedObj.size()); });
245   obj.withWLock([](auto& lockedObj) { lockedObj.pop_back(); });
246 #endif
247
248   // Test withWLockPtr() and withRLockPtr()
249   using SynchType = folly::Synchronized<std::vector<int>, Mutex>;
250 #if __cpp_generic_lambdas >= 201304
251   obj.withWLockPtr([](auto&& lockedObj) { lockedObj->push_back(13); });
252   obj.withRLockPtr([](auto&& lockedObj) {
253     EXPECT_EQ(1003, lockedObj->size());
254     EXPECT_EQ(13, lockedObj->back());
255   });
256   constObj.withRLockPtr([](auto&& lockedObj) {
257     EXPECT_EQ(1003, lockedObj->size());
258     EXPECT_EQ(13, lockedObj->back());
259   });
260   obj.withWLockPtr([&](auto&& lockedObj) {
261     lockedObj->push_back(14);
262     {
263       auto unlocker = lockedObj.scopedUnlock();
264       obj.wlock()->push_back(15);
265     }
266     EXPECT_EQ(15, lockedObj->back());
267   });
268   constObj.withWLockPtr([](auto&& lockedObj) {
269     EXPECT_EQ(1005, lockedObj->size());
270     EXPECT_EQ(15, lockedObj->back());
271   });
272 #else
273   obj.withWLockPtr([](typename SynchType::LockedPtr&& lockedObj) {
274     lockedObj->push_back(13);
275     lockedObj->push_back(14);
276     lockedObj->push_back(15);
277   });
278 #endif
279
280   obj.withWLockPtr([](typename SynchType::LockedPtr&& lockedObj) {
281     lockedObj->push_back(16);
282     EXPECT_EQ(1006, lockedObj->size());
283   });
284   constObj.withWLockPtr([](typename SynchType::ConstWLockedPtr&& lockedObj) {
285     EXPECT_EQ(1006, lockedObj->size());
286     EXPECT_EQ(16, lockedObj->back());
287   });
288   obj.withRLockPtr([](typename SynchType::ConstLockedPtr&& lockedObj) {
289     EXPECT_EQ(1006, lockedObj->size());
290     EXPECT_EQ(16, lockedObj->back());
291   });
292   constObj.withRLockPtr([](typename SynchType::ConstLockedPtr&& lockedObj) {
293     EXPECT_EQ(1006, lockedObj->size());
294     EXPECT_EQ(16, lockedObj->back());
295   });
296 }
297
298 // testWithLock() version for non-shared lock types
299 template <class Mutex>
300 typename std::enable_if<!folly::LockTraits<Mutex>::is_shared>::type
301 testWithLock() {
302   folly::Synchronized<std::vector<int>, Mutex> obj;
303
304   // Test withLock()
305   obj.withLock([](std::vector<int>& lockedObj) {
306     lockedObj.resize(1000);
307     lockedObj.push_back(10);
308     lockedObj.push_back(11);
309   });
310   obj.withLock([](const std::vector<int>& lockedObj) {
311     EXPECT_EQ(1002, lockedObj.size());
312   });
313
314 #if __cpp_generic_lambdas >= 201304
315   obj.withLock([](auto& lockedObj) { lockedObj.push_back(12); });
316   obj.withLock(
317       [](const auto& lockedObj) { EXPECT_EQ(1003, lockedObj.size()); });
318   obj.withLock([](auto& lockedObj) { lockedObj.pop_back(); });
319 #endif
320
321   // Test withLockPtr()
322   using SynchType = folly::Synchronized<std::vector<int>, Mutex>;
323 #if __cpp_generic_lambdas >= 201304
324   obj.withLockPtr([](auto&& lockedObj) { lockedObj->push_back(13); });
325   obj.withLockPtr([](auto&& lockedObj) {
326     EXPECT_EQ(1003, lockedObj->size());
327     EXPECT_EQ(13, lockedObj->back());
328   });
329   obj.withLockPtr([&](auto&& lockedObj) {
330     lockedObj->push_back(14);
331     {
332       auto unlocker = lockedObj.scopedUnlock();
333       obj.lock()->push_back(15);
334     }
335     EXPECT_EQ(1005, lockedObj->size());
336     EXPECT_EQ(15, lockedObj->back());
337   });
338 #else
339   obj.withLockPtr([](typename SynchType::LockedPtr&& lockedObj) {
340     lockedObj->push_back(13);
341     lockedObj->push_back(14);
342     lockedObj->push_back(15);
343   });
344 #endif
345
346   obj.withLockPtr([](typename SynchType::LockedPtr&& lockedObj) {
347     lockedObj->push_back(16);
348     EXPECT_EQ(1006, lockedObj->size());
349   });
350   const auto& constObj = obj;
351   constObj.withLockPtr([](typename SynchType::ConstLockedPtr&& lockedObj) {
352     EXPECT_EQ(1006, lockedObj->size());
353     EXPECT_EQ(16, lockedObj->back());
354   });
355 }
356
357 template <class Mutex>
358 void testUnlockCommon() {
359   folly::Synchronized<int, Mutex> value{7};
360   const auto& cv = value;
361
362   {
363     auto lv = value.contextualLock();
364     EXPECT_EQ(7, *lv);
365     *lv = 5;
366     lv.unlock();
367     EXPECT_TRUE(lv.isNull());
368     EXPECT_FALSE(lv);
369
370     auto rlv = cv.contextualLock();
371     EXPECT_EQ(5, *rlv);
372     rlv.unlock();
373     EXPECT_TRUE(rlv.isNull());
374     EXPECT_FALSE(rlv);
375
376     auto rlv2 = cv.contextualRLock();
377     EXPECT_EQ(5, *rlv2);
378     rlv2.unlock();
379
380     lv = value.contextualLock();
381     EXPECT_EQ(5, *lv);
382     *lv = 9;
383   }
384
385   EXPECT_EQ(9, *value.contextualRLock());
386 }
387
388 // testUnlock() version for shared lock types
389 template <class Mutex>
390 typename std::enable_if<folly::LockTraits<Mutex>::is_shared>::type
391 testUnlock() {
392   folly::Synchronized<int, Mutex> value{10};
393   {
394     auto lv = value.wlock();
395     EXPECT_EQ(10, *lv);
396     *lv = 5;
397     lv.unlock();
398     EXPECT_FALSE(lv);
399     EXPECT_TRUE(lv.isNull());
400
401     auto rlv = value.rlock();
402     EXPECT_EQ(5, *rlv);
403     rlv.unlock();
404     EXPECT_FALSE(rlv);
405     EXPECT_TRUE(rlv.isNull());
406
407     auto lv2 = value.wlock();
408     EXPECT_EQ(5, *lv2);
409     *lv2 = 7;
410
411     lv = std::move(lv2);
412     EXPECT_FALSE(lv2);
413     EXPECT_TRUE(lv2.isNull());
414     EXPECT_FALSE(lv.isNull());
415     EXPECT_EQ(7, *lv);
416   }
417
418   testUnlockCommon<Mutex>();
419 }
420
421 // testUnlock() version for non-shared lock types
422 template <class Mutex>
423 typename std::enable_if<!folly::LockTraits<Mutex>::is_shared>::type
424 testUnlock() {
425   folly::Synchronized<int, Mutex> value{10};
426   {
427     auto lv = value.lock();
428     EXPECT_EQ(10, *lv);
429     *lv = 5;
430     lv.unlock();
431     EXPECT_TRUE(lv.isNull());
432     EXPECT_FALSE(lv);
433
434     auto lv2 = value.lock();
435     EXPECT_EQ(5, *lv2);
436     *lv2 = 6;
437     lv2.unlock();
438     EXPECT_TRUE(lv2.isNull());
439     EXPECT_FALSE(lv2);
440
441     lv = value.lock();
442     EXPECT_EQ(6, *lv);
443     *lv = 7;
444
445     lv2 = std::move(lv);
446     EXPECT_TRUE(lv.isNull());
447     EXPECT_FALSE(lv);
448     EXPECT_FALSE(lv2.isNull());
449     EXPECT_EQ(7, *lv2);
450   }
451
452   testUnlockCommon<Mutex>();
453 }
454
455 // Testing the deprecated SYNCHRONIZED and SYNCHRONIZED_CONST APIs
456 template <class Mutex>
457 void testDeprecated() {
458   folly::Synchronized<std::vector<int>, Mutex> obj;
459
460   obj->resize(1000);
461
462   auto obj2 = obj;
463   EXPECT_EQ(1000, obj2->size());
464
465   SYNCHRONIZED (obj) {
466     obj.push_back(10);
467     EXPECT_EQ(1001, obj.size());
468     EXPECT_EQ(10, obj.back());
469     EXPECT_EQ(1000, obj2->size());
470   }
471
472   SYNCHRONIZED_CONST (obj) {
473     EXPECT_EQ(1001, obj.size());
474   }
475
476   SYNCHRONIZED (lockedObj, *&obj) {
477     lockedObj.front() = 2;
478   }
479
480   EXPECT_EQ(1001, obj->size());
481   EXPECT_EQ(10, obj->back());
482   EXPECT_EQ(1000, obj2->size());
483
484   EXPECT_EQ(FB_ARG_2_OR_1(1, 2), 2);
485   EXPECT_EQ(FB_ARG_2_OR_1(1), 1);
486 }
487
488 template <class Mutex> void testConcurrency() {
489   folly::Synchronized<std::vector<int>, Mutex> v;
490   static const size_t numThreads = 100;
491   // Note: I initially tried using itersPerThread = 1000,
492   // which works fine for most lock types, but std::shared_timed_mutex
493   // appears to be extraordinarily slow.  It could take around 30 seconds
494   // to run this test with 1000 iterations per thread using shared_timed_mutex.
495   static const size_t itersPerThread = 100;
496
497   auto pushNumbers = [&](size_t threadIdx) {
498     // Test lock()
499     for (size_t n = 0; n < itersPerThread; ++n) {
500       v.contextualLock()->push_back((itersPerThread * threadIdx) + n);
501       std::this_thread::yield();
502     }
503   };
504   runParallel(numThreads, pushNumbers);
505
506   std::vector<int> result;
507   v.swap(result);
508
509   EXPECT_EQ(numThreads * itersPerThread, result.size());
510   sort(result.begin(), result.end());
511
512   for (size_t i = 0; i < itersPerThread * numThreads; ++i) {
513     EXPECT_EQ(i, result[i]);
514   }
515 }
516
517 template <class Mutex>
518 void testAcquireLocked() {
519   folly::Synchronized<std::vector<int>, Mutex> v;
520   folly::Synchronized<std::map<int, int>, Mutex> m;
521
522   auto dualLockWorker = [&](size_t threadIdx) {
523     // Note: this will be less awkward with C++ 17's structured
524     // binding functionality, which will make it easier to use the returned
525     // std::tuple.
526     if (threadIdx & 1) {
527       auto ret = acquireLocked(v, m);
528       std::get<0>(ret)->push_back(threadIdx);
529       (*std::get<1>(ret))[threadIdx] = threadIdx + 1;
530     } else {
531       auto ret = acquireLocked(m, v);
532       std::get<1>(ret)->push_back(threadIdx);
533       (*std::get<0>(ret))[threadIdx] = threadIdx + 1;
534     }
535   };
536   static const size_t numThreads = 100;
537   runParallel(numThreads, dualLockWorker);
538
539   std::vector<int> result;
540   v.swap(result);
541
542   EXPECT_EQ(numThreads, result.size());
543   sort(result.begin(), result.end());
544
545   for (size_t i = 0; i < numThreads; ++i) {
546     EXPECT_EQ(i, result[i]);
547   }
548 }
549
550 template <class Mutex>
551 void testAcquireLockedWithConst() {
552   folly::Synchronized<std::vector<int>, Mutex> v;
553   folly::Synchronized<std::map<int, int>, Mutex> m;
554
555   auto dualLockWorker = [&](size_t threadIdx) {
556     const auto& cm = m;
557     if (threadIdx & 1) {
558       auto ret = acquireLocked(v, cm);
559       (void)std::get<1>(ret)->size();
560       std::get<0>(ret)->push_back(threadIdx);
561     } else {
562       auto ret = acquireLocked(cm, v);
563       (void)std::get<0>(ret)->size();
564       std::get<1>(ret)->push_back(threadIdx);
565     }
566   };
567   static const size_t numThreads = 100;
568   runParallel(numThreads, dualLockWorker);
569
570   std::vector<int> result;
571   v.swap(result);
572
573   EXPECT_EQ(numThreads, result.size());
574   sort(result.begin(), result.end());
575
576   for (size_t i = 0; i < numThreads; ++i) {
577     EXPECT_EQ(i, result[i]);
578   }
579 }
580
581 // Testing the deprecated SYNCHRONIZED_DUAL API
582 template <class Mutex> void testDualLocking() {
583   folly::Synchronized<std::vector<int>, Mutex> v;
584   folly::Synchronized<std::map<int, int>, Mutex> m;
585
586   auto dualLockWorker = [&](size_t threadIdx) {
587     if (threadIdx & 1) {
588       SYNCHRONIZED_DUAL(lv, v, lm, m) {
589         lv.push_back(threadIdx);
590         lm[threadIdx] = threadIdx + 1;
591       }
592     } else {
593       SYNCHRONIZED_DUAL(lm, m, lv, v) {
594         lv.push_back(threadIdx);
595         lm[threadIdx] = threadIdx + 1;
596       }
597     }
598   };
599   static const size_t numThreads = 100;
600   runParallel(numThreads, dualLockWorker);
601
602   std::vector<int> result;
603   v.swap(result);
604
605   EXPECT_EQ(numThreads, result.size());
606   sort(result.begin(), result.end());
607
608   for (size_t i = 0; i < numThreads; ++i) {
609     EXPECT_EQ(i, result[i]);
610   }
611 }
612
613 // Testing the deprecated SYNCHRONIZED_DUAL API
614 template <class Mutex> void testDualLockingWithConst() {
615   folly::Synchronized<std::vector<int>, Mutex> v;
616   folly::Synchronized<std::map<int, int>, Mutex> m;
617
618   auto dualLockWorker = [&](size_t threadIdx) {
619     const auto& cm = m;
620     if (threadIdx & 1) {
621       SYNCHRONIZED_DUAL(lv, v, lm, cm) {
622         (void)lm.size();
623         lv.push_back(threadIdx);
624       }
625     } else {
626       SYNCHRONIZED_DUAL(lm, cm, lv, v) {
627         (void)lm.size();
628         lv.push_back(threadIdx);
629       }
630     }
631   };
632   static const size_t numThreads = 100;
633   runParallel(numThreads, dualLockWorker);
634
635   std::vector<int> result;
636   v.swap(result);
637
638   EXPECT_EQ(numThreads, result.size());
639   sort(result.begin(), result.end());
640
641   for (size_t i = 0; i < numThreads; ++i) {
642     EXPECT_EQ(i, result[i]);
643   }
644 }
645
646 template <class Mutex>
647 void testTimed() {
648   folly::Synchronized<std::vector<int>, Mutex> v;
649   folly::Synchronized<uint64_t, Mutex> numTimeouts;
650
651   auto worker = [&](size_t threadIdx) {
652     // Test directly using operator-> on the lock result
653     v.contextualLock()->push_back(2 * threadIdx);
654
655     // Test using lock with a timeout
656     for (;;) {
657       auto lv = v.contextualLock(std::chrono::milliseconds(5));
658       if (!lv) {
659         ++(*numTimeouts.contextualLock());
660         continue;
661       }
662
663       // Sleep for a random time to ensure we trigger timeouts
664       // in other threads
665       randomSleep(std::chrono::milliseconds(5), std::chrono::milliseconds(15));
666       lv->push_back(2 * threadIdx + 1);
667       break;
668     }
669   };
670
671   static const size_t numThreads = 100;
672   runParallel(numThreads, worker);
673
674   std::vector<int> result;
675   v.swap(result);
676
677   EXPECT_EQ(2 * numThreads, result.size());
678   sort(result.begin(), result.end());
679
680   for (size_t i = 0; i < 2 * numThreads; ++i) {
681     EXPECT_EQ(i, result[i]);
682   }
683   // We generally expect a large number of number timeouts here.
684   // I'm not adding a check for it since it's theoretically possible that
685   // we might get 0 timeouts depending on the CPU scheduling if our threads
686   // don't get to run very often.
687   LOG(INFO) << "testTimed: " << *numTimeouts.contextualRLock() << " timeouts";
688
689   // Make sure we can lock with various timeout duration units
690   {
691     auto lv = v.contextualLock(std::chrono::milliseconds(5));
692     EXPECT_TRUE(bool(lv));
693     EXPECT_FALSE(lv.isNull());
694     auto lv2 = v.contextualLock(std::chrono::microseconds(5));
695     // We may or may not acquire lv2 successfully, depending on whether
696     // or not this is a recursive mutex type.
697   }
698   {
699     auto lv = v.contextualLock(std::chrono::seconds(1));
700     EXPECT_TRUE(bool(lv));
701   }
702 }
703
704 template <class Mutex>
705 void testTimedShared() {
706   folly::Synchronized<std::vector<int>, Mutex> v;
707   folly::Synchronized<uint64_t, Mutex> numTimeouts;
708
709   auto worker = [&](size_t threadIdx) {
710     // Test directly using operator-> on the lock result
711     v.wlock()->push_back(threadIdx);
712
713     // Test lock() with a timeout
714     for (;;) {
715       auto lv = v.rlock(std::chrono::milliseconds(10));
716       if (!lv) {
717         ++(*numTimeouts.contextualLock());
718         continue;
719       }
720
721       // Sleep while holding the lock.
722       //
723       // This will block other threads from acquiring the write lock to add
724       // their thread index to v, but it won't block threads that have entered
725       // the for loop and are trying to acquire a read lock.
726       //
727       // For lock types that give preference to readers rather than writers,
728       // this will tend to serialize all threads on the wlock() above.
729       randomSleep(std::chrono::milliseconds(5), std::chrono::milliseconds(15));
730       auto found = std::find(lv->begin(), lv->end(), threadIdx);
731       CHECK(found != lv->end());
732       break;
733     }
734   };
735
736   static const size_t numThreads = 100;
737   runParallel(numThreads, worker);
738
739   std::vector<int> result;
740   v.swap(result);
741
742   EXPECT_EQ(numThreads, result.size());
743   sort(result.begin(), result.end());
744
745   for (size_t i = 0; i < numThreads; ++i) {
746     EXPECT_EQ(i, result[i]);
747   }
748   // We generally expect a small number of timeouts here.
749   // For locks that give readers preference over writers this should usually
750   // be 0.  With locks that give writers preference we do see a small-ish
751   // number of read timeouts.
752   LOG(INFO) << "testTimedShared: " << *numTimeouts.contextualRLock()
753             << " timeouts";
754 }
755
756 // Testing the deprecated TIMED_SYNCHRONIZED API
757 template <class Mutex> void testTimedSynchronized() {
758   folly::Synchronized<std::vector<int>, Mutex> v;
759   folly::Synchronized<uint64_t, Mutex> numTimeouts;
760
761   auto worker = [&](size_t threadIdx) {
762     // Test operator->
763     v->push_back(2 * threadIdx);
764
765     // Aaand test the TIMED_SYNCHRONIZED macro
766     for (;;) {
767       TIMED_SYNCHRONIZED(5, lv, v) {
768         if (lv) {
769           // Sleep for a random time to ensure we trigger timeouts
770           // in other threads
771           randomSleep(
772               std::chrono::milliseconds(5), std::chrono::milliseconds(15));
773           lv->push_back(2 * threadIdx + 1);
774           return;
775         }
776
777         ++(*numTimeouts.contextualLock());
778       }
779     }
780   };
781
782   static const size_t numThreads = 100;
783   runParallel(numThreads, worker);
784
785   std::vector<int> result;
786   v.swap(result);
787
788   EXPECT_EQ(2 * numThreads, result.size());
789   sort(result.begin(), result.end());
790
791   for (size_t i = 0; i < 2 * numThreads; ++i) {
792     EXPECT_EQ(i, result[i]);
793   }
794   // We generally expect a large number of number timeouts here.
795   // I'm not adding a check for it since it's theoretically possible that
796   // we might get 0 timeouts depending on the CPU scheduling if our threads
797   // don't get to run very often.
798   LOG(INFO) << "testTimedSynchronized: " << *numTimeouts.contextualRLock()
799             << " timeouts";
800 }
801
802 // Testing the deprecated TIMED_SYNCHRONIZED_CONST API
803 template <class Mutex> void testTimedSynchronizedWithConst() {
804   folly::Synchronized<std::vector<int>, Mutex> v;
805   folly::Synchronized<uint64_t, Mutex> numTimeouts;
806
807   auto worker = [&](size_t threadIdx) {
808     // Test operator->
809     v->push_back(threadIdx);
810
811     // Test TIMED_SYNCHRONIZED_CONST
812     for (;;) {
813       TIMED_SYNCHRONIZED_CONST(10, lv, v) {
814         if (lv) {
815           // Sleep while holding the lock.
816           //
817           // This will block other threads from acquiring the write lock to add
818           // their thread index to v, but it won't block threads that have
819           // entered the for loop and are trying to acquire a read lock.
820           //
821           // For lock types that give preference to readers rather than writers,
822           // this will tend to serialize all threads on the wlock() above.
823           randomSleep(
824               std::chrono::milliseconds(5), std::chrono::milliseconds(15));
825           auto found = std::find(lv->begin(), lv->end(), threadIdx);
826           CHECK(found != lv->end());
827           return;
828         } else {
829           ++(*numTimeouts.contextualLock());
830         }
831       }
832     }
833   };
834
835   static const size_t numThreads = 100;
836   runParallel(numThreads, worker);
837
838   std::vector<int> result;
839   v.swap(result);
840
841   EXPECT_EQ(numThreads, result.size());
842   sort(result.begin(), result.end());
843
844   for (size_t i = 0; i < numThreads; ++i) {
845     EXPECT_EQ(i, result[i]);
846   }
847   // We generally expect a small number of timeouts here.
848   // For locks that give readers preference over writers this should usually
849   // be 0.  With locks that give writers preference we do see a small-ish
850   // number of read timeouts.
851   LOG(INFO) << "testTimedSynchronizedWithConst: "
852             << *numTimeouts.contextualRLock() << " timeouts";
853 }
854
855 template <class Mutex> void testConstCopy() {
856   std::vector<int> input = {1, 2, 3};
857   const folly::Synchronized<std::vector<int>, Mutex> v(input);
858
859   std::vector<int> result;
860
861   v.copy(&result);
862   EXPECT_EQ(input, result);
863
864   result = v.copy();
865   EXPECT_EQ(input, result);
866 }
867
868 struct NotCopiableNotMovable {
869   NotCopiableNotMovable(int, const char*) {}
870   NotCopiableNotMovable(const NotCopiableNotMovable&) = delete;
871   NotCopiableNotMovable& operator=(const NotCopiableNotMovable&) = delete;
872   NotCopiableNotMovable(NotCopiableNotMovable&&) = delete;
873   NotCopiableNotMovable& operator=(NotCopiableNotMovable&&) = delete;
874 };
875
876 template <class Mutex> void testInPlaceConstruction() {
877   // This won't compile without in_place
878   folly::Synchronized<NotCopiableNotMovable> a(folly::in_place, 5, "a");
879 }
880 }
881 }