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