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