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