1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: sw=4 ts=4 et :
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/ArrayUtils.h"
8
9 #include "prthread.h"
10
11 #include "nsTArray.h"
12 #include "nsMemory.h"
13
14 #include "mozilla/CondVar.h"
15 #include "mozilla/ReentrantMonitor.h"
16 #include "mozilla/Mutex.h"
17
18 #ifdef MOZ_CRASHREPORTER
19 #include "nsCOMPtr.h"
20 #include "nsICrashReporter.h"
21 #include "nsServiceManagerUtils.h"
22 #endif
23
24 #include "gtest/gtest.h"
25
26 using namespace mozilla;
27
28 static PRThread*
spawn(void (* run)(void *),void * arg)29 spawn(void (*run)(void*), void* arg)
30 {
31 return PR_CreateThread(PR_SYSTEM_THREAD,
32 run,
33 arg,
34 PR_PRIORITY_NORMAL,
35 PR_GLOBAL_THREAD,
36 PR_JOINABLE_THREAD,
37 0);
38 }
39
40 // This global variable is defined in toolkit/xre/nsSigHandlers.cpp.
41 extern unsigned int _gdb_sleep_duration;
42
43 /**
44 * Simple test fixture that makes sure the gdb sleep setup in the
45 * ah crap handler is bypassed during the death tests.
46 */
47 class DeadlockDetectorTest : public ::testing::Test
48 {
49 protected:
SetUp()50 void SetUp() final {
51 mOldSleepDuration = _gdb_sleep_duration;
52 _gdb_sleep_duration = 0;
53 }
54
TearDown()55 void TearDown() final {
56 _gdb_sleep_duration = mOldSleepDuration;
57 }
58
59 private:
60 unsigned int mOldSleepDuration;
61 };
62
DisableCrashReporter()63 void DisableCrashReporter()
64 {
65 #ifdef MOZ_CRASHREPORTER
66 nsCOMPtr<nsICrashReporter> crashreporter =
67 do_GetService("@mozilla.org/toolkit/crash-reporter;1");
68 if (crashreporter) {
69 crashreporter->SetEnabled(false);
70 }
71 #endif
72 }
73
74 //-----------------------------------------------------------------------------
75 // Single-threaded sanity tests
76
77 // Stupidest possible deadlock.
78 int
Sanity_Child()79 Sanity_Child()
80 {
81 DisableCrashReporter();
82
83 mozilla::Mutex m1("dd.sanity.m1");
84 m1.Lock();
85 m1.Lock();
86 return 0; // not reached
87 }
88
TEST_F(DeadlockDetectorTest,SanityDeathTest)89 TEST_F(DeadlockDetectorTest, SanityDeathTest)
90 {
91 const char* const regex =
92 "###!!! ERROR: Potential deadlock detected.*"
93 "=== Cyclical dependency starts at.*--- Mutex : dd.sanity.m1.*"
94 "=== Cycle completed at.*--- Mutex : dd.sanity.m1.*"
95 "###!!! Deadlock may happen NOW!.*" // better catch these easy cases...
96 "###!!! ASSERTION: Potential deadlock detected.*";
97
98 ASSERT_DEATH_IF_SUPPORTED(Sanity_Child(), regex);
99 }
100
101 // Slightly less stupid deadlock.
102 int
Sanity2_Child()103 Sanity2_Child()
104 {
105 DisableCrashReporter();
106
107 mozilla::Mutex m1("dd.sanity2.m1");
108 mozilla::Mutex m2("dd.sanity2.m2");
109 m1.Lock();
110 m2.Lock();
111 m1.Lock();
112 return 0; // not reached
113 }
114
TEST_F(DeadlockDetectorTest,Sanity2DeathTest)115 TEST_F(DeadlockDetectorTest, Sanity2DeathTest)
116 {
117 const char* const regex =
118 "###!!! ERROR: Potential deadlock detected.*"
119 "=== Cyclical dependency starts at.*--- Mutex : dd.sanity2.m1.*"
120 "--- Next dependency:.*--- Mutex : dd.sanity2.m2.*"
121 "=== Cycle completed at.*--- Mutex : dd.sanity2.m1.*"
122 "###!!! Deadlock may happen NOW!.*" // better catch these easy cases...
123 "###!!! ASSERTION: Potential deadlock detected.*";
124
125 ASSERT_DEATH_IF_SUPPORTED(Sanity2_Child(), regex);
126 }
127
128 int
Sanity3_Child()129 Sanity3_Child()
130 {
131 DisableCrashReporter();
132
133 mozilla::Mutex m1("dd.sanity3.m1");
134 mozilla::Mutex m2("dd.sanity3.m2");
135 mozilla::Mutex m3("dd.sanity3.m3");
136 mozilla::Mutex m4("dd.sanity3.m4");
137
138 m1.Lock();
139 m2.Lock();
140 m3.Lock();
141 m4.Lock();
142 m4.Unlock();
143 m3.Unlock();
144 m2.Unlock();
145 m1.Unlock();
146
147 m4.Lock();
148 m1.Lock();
149 return 0;
150 }
151
TEST_F(DeadlockDetectorTest,Sanity3DeathTest)152 TEST_F(DeadlockDetectorTest, Sanity3DeathTest)
153 {
154 const char* const regex =
155 "###!!! ERROR: Potential deadlock detected.*"
156 "=== Cyclical dependency starts at.*--- Mutex : dd.sanity3.m1.*"
157 "--- Next dependency:.*--- Mutex : dd.sanity3.m2.*"
158 "--- Next dependency:.*--- Mutex : dd.sanity3.m3.*"
159 "--- Next dependency:.*--- Mutex : dd.sanity3.m4.*"
160 "=== Cycle completed at.*--- Mutex : dd.sanity3.m1.*"
161 "###!!! ASSERTION: Potential deadlock detected.*";
162
163 ASSERT_DEATH_IF_SUPPORTED(Sanity3_Child(), regex);
164 }
165
166 int
Sanity4_Child()167 Sanity4_Child()
168 {
169 DisableCrashReporter();
170
171 mozilla::ReentrantMonitor m1("dd.sanity4.m1");
172 mozilla::Mutex m2("dd.sanity4.m2");
173 m1.Enter();
174 m2.Lock();
175 m1.Enter();
176 return 0;
177 }
178
TEST_F(DeadlockDetectorTest,Sanity4DeathTest)179 TEST_F(DeadlockDetectorTest, Sanity4DeathTest)
180 {
181 const char* const regex =
182 "Re-entering ReentrantMonitor after acquiring other resources.*"
183 "###!!! ERROR: Potential deadlock detected.*"
184 "=== Cyclical dependency starts at.*--- ReentrantMonitor : dd.sanity4.m1.*"
185 "--- Next dependency:.*--- Mutex : dd.sanity4.m2.*"
186 "=== Cycle completed at.*--- ReentrantMonitor : dd.sanity4.m1.*"
187 "###!!! ASSERTION: Potential deadlock detected.*";
188 ASSERT_DEATH_IF_SUPPORTED(Sanity4_Child(), regex);
189 }
190
191 //-----------------------------------------------------------------------------
192 // Multithreaded tests
193
194 /**
195 * Helper for passing state to threads in the multithread tests.
196 */
197 struct ThreadState
198 {
199 /**
200 * Locks to use during the test. This is just a reference and is owned by
201 * the main test thread.
202 */
203 const nsTArray<mozilla::Mutex*>& locks;
204
205 /**
206 * Integer argument used to identify each thread.
207 */
208 int id;
209 };
210
211 static void
TwoThreads_thread(void * arg)212 TwoThreads_thread(void* arg)
213 {
214 ThreadState* state = static_cast<ThreadState*>(arg);
215
216 mozilla::Mutex* ttM1 = state->locks[0];
217 mozilla::Mutex* ttM2 = state->locks[1];
218
219 if (state->id) {
220 ttM1->Lock();
221 ttM2->Lock();
222 ttM2->Unlock();
223 ttM1->Unlock();
224 }
225 else {
226 ttM2->Lock();
227 ttM1->Lock();
228 ttM1->Unlock();
229 ttM2->Unlock();
230 }
231 }
232
233 int
TwoThreads_Child()234 TwoThreads_Child()
235 {
236 DisableCrashReporter();
237
238 nsTArray<mozilla::Mutex*> locks = {
239 new mozilla::Mutex("dd.twothreads.m1"),
240 new mozilla::Mutex("dd.twothreads.m2")
241 };
242
243 ThreadState state_1 {locks, 0};
244 PRThread* t1 = spawn(TwoThreads_thread, &state_1);
245 PR_JoinThread(t1);
246
247 ThreadState state_2 {locks, 1};
248 PRThread* t2 = spawn(TwoThreads_thread, &state_2);
249 PR_JoinThread(t2);
250
251 for (auto& lock : locks) {
252 delete lock;
253 }
254
255 return 0;
256 }
257
TEST_F(DeadlockDetectorTest,TwoThreadsDeathTest)258 TEST_F(DeadlockDetectorTest, TwoThreadsDeathTest)
259 {
260 const char* const regex =
261 "###!!! ERROR: Potential deadlock detected.*"
262 "=== Cyclical dependency starts at.*--- Mutex : dd.twothreads.m2.*"
263 "--- Next dependency:.*--- Mutex : dd.twothreads.m1.*"
264 "=== Cycle completed at.*--- Mutex : dd.twothreads.m2.*"
265 "###!!! ASSERTION: Potential deadlock detected.*";
266
267 ASSERT_DEATH_IF_SUPPORTED(TwoThreads_Child(), regex);
268 }
269
270 static void
ContentionNoDeadlock_thread(void * arg)271 ContentionNoDeadlock_thread(void* arg)
272 {
273 const uint32_t K = 100000;
274
275 ThreadState* state = static_cast<ThreadState*>(arg);
276 int32_t starti = static_cast<int32_t>(state->id);
277 auto& cndMs = state->locks;
278
279 for (uint32_t k = 0; k < K; ++k) {
280 for (int32_t i = starti; i < (int32_t)cndMs.Length(); ++i)
281 cndMs[i]->Lock();
282 // comment out the next two lines for deadlocking fun!
283 for (int32_t i = cndMs.Length() - 1; i >= starti; --i)
284 cndMs[i]->Unlock();
285
286 starti = (starti + 1) % 3;
287 }
288 }
289
290 int
ContentionNoDeadlock_Child()291 ContentionNoDeadlock_Child()
292 {
293 const size_t kMutexCount = 4;
294
295 PRThread* threads[3];
296 nsTArray<mozilla::Mutex*> locks;
297 ThreadState states[] = {
298 { locks, 0 },
299 { locks, 1 },
300 { locks, 2 }
301 };
302
303 for (uint32_t i = 0; i < kMutexCount; ++i)
304 locks.AppendElement(new mozilla::Mutex("dd.cnd.ms"));
305
306 for (int32_t i = 0; i < (int32_t) ArrayLength(threads); ++i)
307 threads[i] = spawn(ContentionNoDeadlock_thread, states + i);
308
309 for (uint32_t i = 0; i < ArrayLength(threads); ++i)
310 PR_JoinThread(threads[i]);
311
312 for (uint32_t i = 0; i < locks.Length(); ++i)
313 delete locks[i];
314
315 return 0;
316 }
317
TEST_F(DeadlockDetectorTest,ContentionNoDeadlock)318 TEST_F(DeadlockDetectorTest, ContentionNoDeadlock)
319 {
320 // Just check that this test runs to completion.
321 ASSERT_EQ(ContentionNoDeadlock_Child(), 0);
322 }
323