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
32DEFINE_int32(num_threads, 8, "num threads");
33
34namespace {
35
36static const int kMaxReaders = 50;
37static std::atomic<bool> stopThread;
38using namespace folly;
39
40template <typename RWSpinLockT>
41struct RWSpinLockTest : public testing::Test {
42 typedef RWSpinLockT RWSpinLockType;
43};
44
45typedef 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
57TYPED_TEST_CASE(RWSpinLockTest, Implementations);
58
59template <typename RWSpinLockType>
60static 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
75TYPED_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
92TYPED_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
108TYPED_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
120TYPED_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
137TYPED_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
152TYPED_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
172TEST(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
193TEST(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