OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "mojo/public/cpp/utility/mutex.h" | |
6 | |
7 #include <stddef.h> | |
8 #include <stdlib.h> // For |rand()|. | |
9 #include <time.h> // For |nanosleep()| (defined by POSIX). | |
10 | |
11 #include <vector> | |
12 | |
13 #include "base/compiler_specific.h" | |
14 #include "mojo/public/cpp/system/macros.h" | |
15 #include "mojo/public/cpp/utility/thread.h" | |
16 #include "testing/gtest/include/gtest/gtest.h" | |
17 | |
18 namespace mojo { | |
19 namespace { | |
20 | |
21 TEST(MutexTest, TrivialSingleThreaded) { | |
22 Mutex mutex; | |
23 | |
24 mutex.Lock(); | |
25 mutex.AssertHeld(); | |
26 mutex.Unlock(); | |
27 | |
28 EXPECT_TRUE(mutex.TryLock()); | |
29 mutex.AssertHeld(); | |
30 mutex.Unlock(); | |
31 | |
32 { | |
33 MutexLock lock(&mutex); | |
34 mutex.AssertHeld(); | |
35 } | |
36 | |
37 EXPECT_TRUE(mutex.TryLock()); | |
38 mutex.Unlock(); | |
39 } | |
40 | |
41 class Fiddler { | |
42 public: | |
43 enum Type { kTypeLock, kTypeTry }; | |
44 Fiddler(size_t times_to_lock, | |
45 Type type, | |
46 bool should_sleep, | |
47 Mutex* mutex, | |
48 int* shared_value) | |
49 : times_to_lock_(times_to_lock), | |
50 type_(type), | |
51 should_sleep_(should_sleep), | |
52 mutex_(mutex), | |
53 shared_value_(shared_value) { | |
54 } | |
55 | |
56 ~Fiddler() { | |
57 } | |
58 | |
59 void Fiddle() { | |
60 for (size_t i = 0; i < times_to_lock_;) { | |
61 switch (type_) { | |
62 case kTypeLock: { | |
63 mutex_->Lock(); | |
64 int old_shared_value = *shared_value_; | |
65 if (should_sleep_) | |
66 SleepALittle(); | |
67 *shared_value_ = old_shared_value + 1; | |
68 mutex_->Unlock(); | |
69 i++; | |
70 break; | |
71 } | |
72 case kTypeTry: | |
73 if (mutex_->TryLock()) { | |
74 int old_shared_value = *shared_value_; | |
75 if (should_sleep_) | |
76 SleepALittle(); | |
77 *shared_value_ = old_shared_value + 1; | |
78 mutex_->Unlock(); | |
79 i++; | |
80 } else { | |
81 SleepALittle(); // Don't spin. | |
82 } | |
83 break; | |
84 } | |
85 } | |
86 } | |
87 | |
88 private: | |
89 static void SleepALittle() { | |
90 static const long kNanosPerMilli = 1000000; | |
91 struct timespec req = { | |
92 0, // Seconds. | |
93 (rand() % 10) * kNanosPerMilli // Nanoseconds. | |
94 }; | |
95 int rv = nanosleep(&req, nullptr); | |
96 ALLOW_UNUSED_LOCAL(rv); | |
97 assert(rv == 0); | |
98 } | |
99 | |
100 const size_t times_to_lock_; | |
101 const Type type_; | |
102 const bool should_sleep_; | |
103 Mutex* const mutex_; | |
104 int* const shared_value_; | |
105 | |
106 DISALLOW_COPY_AND_ASSIGN(Fiddler); | |
107 }; | |
108 | |
109 class FiddlerThread : public Thread { | |
110 public: | |
111 // Takes ownership of |fiddler|. | |
112 FiddlerThread(Fiddler* fiddler) | |
113 : fiddler_(fiddler) { | |
114 } | |
115 | |
116 ~FiddlerThread() override { delete fiddler_; } | |
117 | |
118 void Run() override { fiddler_->Fiddle(); } | |
119 | |
120 private: | |
121 Fiddler* const fiddler_; | |
122 | |
123 DISALLOW_COPY_AND_ASSIGN(FiddlerThread); | |
124 }; | |
125 | |
126 // This does a stress test (that also checks exclusion). | |
127 TEST(MutexTest, ThreadedStress) { | |
128 static const size_t kNumThreads = 20; | |
129 static const int kTimesToLockEach = 20; | |
130 assert(kNumThreads % 4 == 0); | |
131 | |
132 Mutex mutex; | |
133 int shared_value = 0; | |
134 | |
135 std::vector<FiddlerThread*> fiddler_threads; | |
136 | |
137 for (size_t i = 0; i < kNumThreads; i += 4) { | |
138 fiddler_threads.push_back(new FiddlerThread(new Fiddler( | |
139 kTimesToLockEach, Fiddler::kTypeLock, false, &mutex, &shared_value))); | |
140 fiddler_threads.push_back(new FiddlerThread(new Fiddler( | |
141 kTimesToLockEach, Fiddler::kTypeTry, false, &mutex, &shared_value))); | |
142 fiddler_threads.push_back(new FiddlerThread(new Fiddler( | |
143 kTimesToLockEach, Fiddler::kTypeLock, true, &mutex, &shared_value))); | |
144 fiddler_threads.push_back(new FiddlerThread(new Fiddler( | |
145 kTimesToLockEach, Fiddler::kTypeTry, true, &mutex, &shared_value))); | |
146 } | |
147 | |
148 for (size_t i = 0; i < kNumThreads; i++) | |
149 fiddler_threads[i]->Start(); | |
150 | |
151 // Do some fiddling ourselves. | |
152 Fiddler(kTimesToLockEach, Fiddler::kTypeLock, true, &mutex, &shared_value) | |
153 .Fiddle(); | |
154 | |
155 // Join. | |
156 for (size_t i = 0; i < kNumThreads; i++) | |
157 fiddler_threads[i]->Join(); | |
158 | |
159 EXPECT_EQ(static_cast<int>(kNumThreads + 1) * kTimesToLockEach, shared_value); | |
160 | |
161 // Delete. | |
162 for (size_t i = 0; i < kNumThreads; i++) | |
163 delete fiddler_threads[i]; | |
164 fiddler_threads.clear(); | |
165 } | |
166 | |
167 class TryThread : public Thread { | |
168 public: | |
169 explicit TryThread(Mutex* mutex) : mutex_(mutex), try_lock_succeeded_() {} | |
170 ~TryThread() override {} | |
171 | |
172 void Run() override { | |
173 try_lock_succeeded_ = mutex_->TryLock(); | |
174 if (try_lock_succeeded_) | |
175 mutex_->Unlock(); | |
176 } | |
177 | |
178 bool try_lock_succeeded() const { return try_lock_succeeded_; } | |
179 | |
180 private: | |
181 Mutex* const mutex_; | |
182 bool try_lock_succeeded_; | |
183 | |
184 DISALLOW_COPY_AND_ASSIGN(TryThread); | |
185 }; | |
186 | |
187 TEST(MutexTest, TryLock) { | |
188 Mutex mutex; | |
189 | |
190 // |TryLock()| should succeed -- we don't have the lock. | |
191 { | |
192 TryThread thread(&mutex); | |
193 thread.Start(); | |
194 thread.Join(); | |
195 EXPECT_TRUE(thread.try_lock_succeeded()); | |
196 } | |
197 | |
198 // Take the lock. | |
199 ASSERT_TRUE(mutex.TryLock()); | |
200 | |
201 // Now it should fail. | |
202 { | |
203 TryThread thread(&mutex); | |
204 thread.Start(); | |
205 thread.Join(); | |
206 EXPECT_FALSE(thread.try_lock_succeeded()); | |
207 } | |
208 | |
209 // Release the lock. | |
210 mutex.Unlock(); | |
211 | |
212 // It should succeed again. | |
213 { | |
214 TryThread thread(&mutex); | |
215 thread.Start(); | |
216 thread.Join(); | |
217 EXPECT_TRUE(thread.try_lock_succeeded()); | |
218 } | |
219 } | |
220 | |
221 | |
222 // Tests of assertions for Debug builds. | |
223 #if !defined(NDEBUG) | |
224 // Test |AssertHeld()| (which is an actual user API). | |
225 TEST(MutexTest, DebugAssertHeldFailure) { | |
226 Mutex mutex; | |
227 EXPECT_DEATH_IF_SUPPORTED(mutex.AssertHeld(), ""); | |
228 } | |
229 | |
230 // Test other consistency checks. | |
231 TEST(MutexTest, DebugAssertionFailures) { | |
232 // Unlock without lock held. | |
233 EXPECT_DEATH_IF_SUPPORTED({ | |
234 Mutex mutex; | |
235 mutex.Unlock(); | |
236 }, ""); | |
237 | |
238 // Lock with lock held (on same thread). | |
239 EXPECT_DEATH_IF_SUPPORTED({ | |
240 Mutex mutex; | |
241 mutex.Lock(); | |
242 mutex.Lock(); | |
243 }, ""); | |
244 | |
245 // Try lock with lock held. | |
246 EXPECT_DEATH_IF_SUPPORTED({ | |
247 Mutex mutex; | |
248 mutex.Lock(); | |
249 mutex.TryLock(); | |
250 }, ""); | |
251 | |
252 // Destroy lock with lock held. | |
253 EXPECT_DEATH_IF_SUPPORTED({ | |
254 Mutex mutex; | |
255 mutex.Lock(); | |
256 }, ""); | |
257 } | |
258 #endif // !defined(NDEBUG) | |
259 | |
260 } // namespace | |
261 } // namespace mojo | |
OLD | NEW |