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