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