1 | /* |
2 | * Copyright 2011-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 | // @author xliu (xliux@fb.com) |
18 | // |
19 | |
20 | #include <folly/synchronization/RWSpinLock.h> |
21 | |
22 | #include <stdlib.h> |
23 | #include <thread> |
24 | #include <vector> |
25 | |
26 | #include <glog/logging.h> |
27 | |
28 | #include <folly/portability/GFlags.h> |
29 | #include <folly/portability/GTest.h> |
30 | #include <folly/portability/Unistd.h> |
31 | |
32 | DEFINE_int32(num_threads, 8, "num threads" ); |
33 | |
34 | namespace { |
35 | |
36 | static const int kMaxReaders = 50; |
37 | static std::atomic<bool> stopThread; |
38 | using namespace folly; |
39 | |
40 | template <typename RWSpinLockT> |
41 | struct RWSpinLockTest : public testing::Test { |
42 | typedef RWSpinLockT RWSpinLockType; |
43 | }; |
44 | |
45 | typedef testing::Types< |
46 | RWSpinLock |
47 | #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_ |
48 | , |
49 | RWTicketSpinLockT<32, true>, |
50 | RWTicketSpinLockT<32, false>, |
51 | RWTicketSpinLockT<64, true>, |
52 | RWTicketSpinLockT<64, false> |
53 | #endif |
54 | > |
55 | Implementations; |
56 | |
57 | TYPED_TEST_CASE(RWSpinLockTest, Implementations); |
58 | |
59 | template <typename RWSpinLockType> |
60 | static void run(RWSpinLockType* lock) { |
61 | int64_t reads = 0; |
62 | int64_t writes = 0; |
63 | while (!stopThread.load(std::memory_order_acquire)) { |
64 | if (rand() % 10 == 0) { // write |
65 | typename RWSpinLockType::WriteHolder guard(lock); |
66 | ++writes; |
67 | } else { // read |
68 | typename RWSpinLockType::ReadHolder guard(lock); |
69 | ++reads; |
70 | } |
71 | } |
72 | // VLOG(0) << "total reads: " << reads << "; total writes: " << writes; |
73 | } |
74 | |
75 | TYPED_TEST(RWSpinLockTest, Writer_Wait_Readers) { |
76 | typedef typename TestFixture::RWSpinLockType LockType; |
77 | LockType l; |
78 | |
79 | for (int i = 0; i < kMaxReaders; ++i) { |
80 | EXPECT_TRUE(l.try_lock_shared()); |
81 | EXPECT_FALSE(l.try_lock()); |
82 | } |
83 | |
84 | for (int i = 0; i < kMaxReaders; ++i) { |
85 | EXPECT_FALSE(l.try_lock()); |
86 | l.unlock_shared(); |
87 | } |
88 | |
89 | EXPECT_TRUE(l.try_lock()); |
90 | } |
91 | |
92 | TYPED_TEST(RWSpinLockTest, Readers_Wait_Writer) { |
93 | typedef typename TestFixture::RWSpinLockType LockType; |
94 | LockType l; |
95 | |
96 | EXPECT_TRUE(l.try_lock()); |
97 | |
98 | for (int i = 0; i < kMaxReaders; ++i) { |
99 | EXPECT_FALSE(l.try_lock_shared()); |
100 | } |
101 | |
102 | l.unlock_and_lock_shared(); |
103 | for (int i = 0; i < kMaxReaders - 1; ++i) { |
104 | EXPECT_TRUE(l.try_lock_shared()); |
105 | } |
106 | } |
107 | |
108 | TYPED_TEST(RWSpinLockTest, Writer_Wait_Writer) { |
109 | typedef typename TestFixture::RWSpinLockType LockType; |
110 | LockType l; |
111 | |
112 | EXPECT_TRUE(l.try_lock()); |
113 | EXPECT_FALSE(l.try_lock()); |
114 | l.unlock(); |
115 | |
116 | EXPECT_TRUE(l.try_lock()); |
117 | EXPECT_FALSE(l.try_lock()); |
118 | } |
119 | |
120 | TYPED_TEST(RWSpinLockTest, Read_Holders) { |
121 | typedef typename TestFixture::RWSpinLockType LockType; |
122 | LockType l; |
123 | |
124 | { |
125 | typename LockType::ReadHolder guard(&l); |
126 | EXPECT_FALSE(l.try_lock()); |
127 | EXPECT_TRUE(l.try_lock_shared()); |
128 | l.unlock_shared(); |
129 | |
130 | EXPECT_FALSE(l.try_lock()); |
131 | } |
132 | |
133 | EXPECT_TRUE(l.try_lock()); |
134 | l.unlock(); |
135 | } |
136 | |
137 | TYPED_TEST(RWSpinLockTest, Write_Holders) { |
138 | typedef typename TestFixture::RWSpinLockType LockType; |
139 | LockType l; |
140 | { |
141 | typename LockType::WriteHolder guard(&l); |
142 | EXPECT_FALSE(l.try_lock()); |
143 | EXPECT_FALSE(l.try_lock_shared()); |
144 | } |
145 | |
146 | EXPECT_TRUE(l.try_lock_shared()); |
147 | EXPECT_FALSE(l.try_lock()); |
148 | l.unlock_shared(); |
149 | EXPECT_TRUE(l.try_lock()); |
150 | } |
151 | |
152 | TYPED_TEST(RWSpinLockTest, ConcurrentTests) { |
153 | typedef typename TestFixture::RWSpinLockType LockType; |
154 | LockType l; |
155 | srand(time(nullptr)); |
156 | |
157 | std::vector<std::thread> threads; |
158 | for (int i = 0; i < FLAGS_num_threads; ++i) { |
159 | threads.push_back(std::thread(&run<LockType>, &l)); |
160 | } |
161 | |
162 | sleep(1); |
163 | stopThread.store(true, std::memory_order_release); |
164 | |
165 | for (auto& t : threads) { |
166 | t.join(); |
167 | } |
168 | } |
169 | |
170 | // RWSpinLock specific tests |
171 | |
172 | TEST(RWSpinLock, lock_unlock_tests) { |
173 | folly::RWSpinLock lock; |
174 | EXPECT_TRUE(lock.try_lock_upgrade()); |
175 | EXPECT_FALSE(lock.try_lock_shared()); |
176 | EXPECT_FALSE(lock.try_lock()); |
177 | EXPECT_FALSE(lock.try_lock_upgrade()); |
178 | lock.unlock_upgrade(); |
179 | lock.lock_shared(); |
180 | EXPECT_FALSE(lock.try_lock()); |
181 | EXPECT_TRUE(lock.try_lock_upgrade()); |
182 | lock.unlock_upgrade(); |
183 | lock.unlock_shared(); |
184 | EXPECT_TRUE(lock.try_lock()); |
185 | EXPECT_FALSE(lock.try_lock_upgrade()); |
186 | lock.unlock_and_lock_upgrade(); |
187 | EXPECT_FALSE(lock.try_lock_shared()); |
188 | lock.unlock_upgrade_and_lock_shared(); |
189 | lock.unlock_shared(); |
190 | EXPECT_EQ(0, lock.bits()); |
191 | } |
192 | |
193 | TEST(RWSpinLock, concurrent_holder_test) { |
194 | srand(time(nullptr)); |
195 | |
196 | folly::RWSpinLock lock; |
197 | std::atomic<int64_t> reads(0); |
198 | std::atomic<int64_t> writes(0); |
199 | std::atomic<int64_t> upgrades(0); |
200 | std::atomic<bool> stop(false); |
201 | |
202 | auto go = [&] { |
203 | while (!stop.load(std::memory_order_acquire)) { |
204 | auto r = (uint32_t)(rand()) % 10; |
205 | if (r < 3) { // starts from write lock |
206 | RWSpinLock::ReadHolder rg{ |
207 | RWSpinLock::UpgradedHolder{RWSpinLock::WriteHolder{&lock}}}; |
208 | writes.fetch_add(1, std::memory_order_acq_rel); |
209 | } else if (r < 6) { // starts from upgrade lock |
210 | RWSpinLock::UpgradedHolder ug(&lock); |
211 | if (r < 4) { |
212 | RWSpinLock::WriteHolder wg(std::move(ug)); |
213 | } else { |
214 | RWSpinLock::ReadHolder rg(std::move(ug)); |
215 | } |
216 | upgrades.fetch_add(1, std::memory_order_acq_rel); |
217 | } else { |
218 | RWSpinLock::ReadHolder rg{&lock}; |
219 | reads.fetch_add(1, std::memory_order_acq_rel); |
220 | } |
221 | } |
222 | }; |
223 | |
224 | std::vector<std::thread> threads; |
225 | for (int i = 0; i < FLAGS_num_threads; ++i) { |
226 | threads.push_back(std::thread(go)); |
227 | } |
228 | |
229 | sleep(5); |
230 | stop.store(true, std::memory_order_release); |
231 | |
232 | for (auto& t : threads) { |
233 | t.join(); |
234 | } |
235 | |
236 | LOG(INFO) << "reads: " << reads.load(std::memory_order_acquire) |
237 | << "; writes: " << writes.load(std::memory_order_acquire) |
238 | << "; upgrades: " << upgrades.load(std::memory_order_acquire); |
239 | } |
240 | |
241 | } // namespace |
242 | |