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