Extract WaitOptions
[folly.git] / folly / synchronization / test / SaturatingSemaphoreTest.cpp
1 /*
2  * Copyright 2017-present 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 <folly/synchronization/SaturatingSemaphore.h>
18 #include <folly/portability/GTest.h>
19 #include <folly/test/DeterministicSchedule.h>
20
21 /// Test helper functions
22
23 using folly::SaturatingSemaphore;
24 using DSched = folly::test::DeterministicSchedule;
25
26 template <bool MayBlock, template <typename> class Atom = std::atomic>
27 void run_basic_test() {
28   SaturatingSemaphore<MayBlock, Atom> f;
29   ASSERT_FALSE(f.ready());
30   ASSERT_FALSE(f.try_wait());
31   ASSERT_FALSE(f.try_wait_until(
32       std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
33   ASSERT_FALSE(f.try_wait_until(
34       std::chrono::steady_clock::now() + std::chrono::microseconds(1),
35       f.wait_options().spin_max(std::chrono::microseconds(1))));
36   f.post();
37   f.post();
38   f.wait();
39   f.wait(f.wait_options().spin_max(std::chrono::nanoseconds(100)));
40   ASSERT_TRUE(f.ready());
41   ASSERT_TRUE(f.try_wait());
42   ASSERT_TRUE(f.try_wait_until(
43       std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
44   f.wait();
45   f.reset();
46   ASSERT_FALSE(f.try_wait());
47 }
48
49 template <bool MayBlock, template <typename> class Atom = std::atomic>
50 void run_pingpong_test(int numRounds) {
51   using WF = SaturatingSemaphore<MayBlock, Atom>;
52   std::array<WF, 17> flags;
53   WF& a = flags[0];
54   WF& b = flags[16]; // different cache line
55   auto thr = DSched::thread([&] {
56     for (int i = 0; i < numRounds; ++i) {
57       a.try_wait();
58       a.wait();
59       a.reset();
60       b.post();
61     }
62   });
63   for (int i = 0; i < numRounds; ++i) {
64     a.post();
65     b.try_wait();
66     b.wait();
67     b.reset();
68   }
69   DSched::join(thr);
70 }
71
72 template <bool MayBlock, template <typename> class Atom = std::atomic>
73 void run_multi_poster_multi_waiter_test(int np, int nw) {
74   SaturatingSemaphore<MayBlock, Atom> f;
75   std::atomic<int> posted{0};
76   std::atomic<int> waited{0};
77   std::atomic<bool> go_post{false};
78   std::atomic<bool> go_wait{false};
79
80   std::vector<std::thread> prod(np);
81   std::vector<std::thread> cons(nw);
82   for (int i = 0; i < np; ++i) {
83     prod[i] = DSched::thread([&] {
84       while (!go_post.load()) {
85         /* spin */;
86       }
87       f.post();
88       posted.fetch_add(1);
89     });
90   }
91
92   for (int i = 0; i < nw; ++i) {
93     cons[i] = DSched::thread([&] {
94       ASSERT_FALSE(f.ready());
95       ASSERT_FALSE(f.try_wait());
96       ASSERT_FALSE(f.try_wait_for(std::chrono::microseconds(1)));
97       ASSERT_FALSE(f.try_wait_until(
98           std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
99       ASSERT_FALSE(f.try_wait_until(
100           std::chrono::steady_clock::now() + std::chrono::microseconds(1),
101           f.wait_options().spin_max(std::chrono::microseconds(0))));
102       waited.fetch_add(1);
103       while (!go_wait.load()) {
104         /* spin */;
105       }
106       ASSERT_TRUE(f.ready());
107       ASSERT_TRUE(f.try_wait());
108       ASSERT_TRUE(f.try_wait_for(std::chrono::microseconds(1)));
109       ASSERT_TRUE(f.try_wait_until(
110           std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
111       ASSERT_TRUE(f.try_wait_until(
112           std::chrono::steady_clock::now() + std::chrono::microseconds(1),
113           f.wait_options().spin_max(std::chrono::microseconds(0))));
114       f.wait();
115     });
116   }
117
118   while (waited.load() < nw) {
119     /* spin */;
120   }
121   go_post.store(true);
122   while (posted.load() < np) {
123     /* spin */;
124   }
125   go_wait.store(true);
126
127   for (auto& t : prod) {
128     DSched::join(t);
129   }
130   for (auto& t : cons) {
131     DSched::join(t);
132   }
133 }
134
135 /// Tests
136
137 TEST(SaturatingSemaphore, basic) {
138   run_basic_test<false>();
139   run_basic_test<true>();
140 }
141
142 TEST(SaturatingSemaphore, pingpong) {
143   run_pingpong_test<false>(1000);
144   run_pingpong_test<true>(1000);
145 }
146
147 TEST(SaturatingSemaphore, multi_poster_multi_waiter) {
148   run_multi_poster_multi_waiter_test<false>(1, 1);
149   run_multi_poster_multi_waiter_test<false>(1, 10);
150   run_multi_poster_multi_waiter_test<false>(10, 1);
151   run_multi_poster_multi_waiter_test<false>(10, 10);
152   run_multi_poster_multi_waiter_test<true>(1, 1);
153   run_multi_poster_multi_waiter_test<true>(1, 10);
154   run_multi_poster_multi_waiter_test<true>(10, 1);
155   run_multi_poster_multi_waiter_test<true>(10, 10);
156 }