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
16namespace mojo {
17namespace {
18
19TEST(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
39class Fiddler {
40 public:
41  enum Type { kTypeLock, kTypeTry };
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
54  ~Fiddler() {
55  }
56
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:
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
106class FiddlerThread : public Thread {
107 public:
108  // Takes ownership of |fiddler|.
109  FiddlerThread(Fiddler* fiddler)
110      : fiddler_(fiddler) {
111  }
112
113  virtual ~FiddlerThread() {
114    delete fiddler_;
115  }
116
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).
128TEST(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
168class TryThread : public Thread {
169 public:
170  explicit TryThread(Mutex* mutex) : mutex_(mutex), try_lock_succeeded_() {}
171  virtual ~TryThread() {}
172
173  virtual void Run() MOJO_OVERRIDE {
174    try_lock_succeeded_ = mutex_->TryLock();
175    if (try_lock_succeeded_)
176      mutex_->Unlock();
177  }
178
179  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
188TEST(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).
226TEST(MutexTest, DebugAssertHeldFailure) {
227  Mutex mutex;
228  EXPECT_DEATH_IF_SUPPORTED(mutex.AssertHeld(), "");
229}
230
231// Test other consistency checks.
232TEST(MutexTest, DebugAssertionFailures) {
233  // Unlock without lock held.
234  EXPECT_DEATH_IF_SUPPORTED({
235    Mutex mutex;
236    mutex.Unlock();
237  }, "");
238
239  // Lock with lock held (on same thread).
240  EXPECT_DEATH_IF_SUPPORTED({
241    Mutex mutex;
242    mutex.Lock();
243    mutex.Lock();
244  }, "");
245
246  // Try lock with lock held.
247  EXPECT_DEATH_IF_SUPPORTED({
248    Mutex mutex;
249    mutex.Lock();
250    mutex.TryLock();
251  }, "");
252
253  // Destroy lock with lock held.
254  EXPECT_DEATH_IF_SUPPORTED({
255    Mutex mutex;
256    mutex.Lock();
257  }, "");
258}
259#endif  // !defined(NDEBUG)
260
261}  // namespace
262}  // namespace mojo
263