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