Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(75)

Side by Side Diff: base/win/wait_chain_unittest.cc

Issue 1834463002: Identify the hung thread using the Wait Chain Traversal API (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: BASE_EXPORT Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « base/win/wait_chain.cc ('k') | chrome/browser/process_singleton_win.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2016 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 "base/win/wait_chain.h"
6
7 #include <memory>
8 #include <string>
9
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_piece.h"
14 #include "base/test/multiprocess_test.h"
15 #include "base/threading/simple_thread.h"
16 #include "base/win/win_util.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "testing/multiprocess_func_list.h"
19
20 namespace base {
21 namespace win {
22
23 namespace {
24
25 // Appends |handle| as a command line switch.
26 void AppendSwitchHandle(CommandLine* command_line,
27 StringPiece switch_name,
28 HANDLE handle) {
29 command_line->AppendSwitchASCII(switch_name.as_string(),
30 UintToString(HandleToUint32(handle)));
31 }
32
33 // Retrieves the |handle| associated to |switch_name| from the command line.
34 ScopedHandle GetSwitchValueHandle(CommandLine* command_line,
35 StringPiece switch_name) {
36 std::string switch_string =
37 command_line->GetSwitchValueASCII(switch_name.as_string());
38 unsigned int switch_uint = 0;
39 if (switch_string.empty() || !StringToUint(switch_string, &switch_uint)) {
40 DLOG(ERROR) << "Missing or invalid " << switch_name << " argument.";
41 return ScopedHandle();
42 }
43 return ScopedHandle(reinterpret_cast<HANDLE>(switch_uint));
44 }
45
46 // Helper function to create a mutex.
47 ScopedHandle CreateMutex(bool inheritable) {
48 SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES),
49 nullptr, inheritable};
50 return ScopedHandle(::CreateMutex(&security_attributes, FALSE, NULL));
51 }
52
53 // Helper function to create an event.
54 ScopedHandle CreateEvent(bool inheritable) {
55 SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES),
56 nullptr, inheritable};
57 return ScopedHandle(
58 ::CreateEvent(&security_attributes, FALSE, FALSE, nullptr));
59 }
60
61 // Helper thread class that runs the callback then stops.
62 class SingleTaskThread : public SimpleThread {
63 public:
64 explicit SingleTaskThread(const Closure& task)
65 : SimpleThread("WaitChainTest SingleTaskThread"), task_(task) {}
66
67 void Run() override { task_.Run(); }
68
69 private:
70 Closure task_;
71
72 DISALLOW_COPY_AND_ASSIGN(SingleTaskThread);
73 };
74
75 // Helper thread to cause a deadlock by acquiring 2 mutexes in a given order.
76 class DeadlockThread : public SimpleThread {
77 public:
78 DeadlockThread(HANDLE mutex_1, HANDLE mutex_2)
79 : SimpleThread("WaitChainTest DeadlockThread"),
80 wait_event_(CreateEvent(false)),
81 mutex_acquired_event_(CreateEvent(false)),
82 mutex_1_(mutex_1),
83 mutex_2_(mutex_2) {}
84
85 void Run() override {
86 // Acquire the mutex then signal the main thread.
87 EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(mutex_1_, INFINITE));
88 EXPECT_TRUE(::SetEvent(mutex_acquired_event_.Get()));
89
90 // Wait until both threads are holding their mutex before trying to acquire
91 // the other one.
92 EXPECT_EQ(WAIT_OBJECT_0,
93 ::WaitForSingleObject(wait_event_.Get(), INFINITE));
94
95 // To unblock the deadlock, one of the threads will get terminated (via
96 // TerminateThread()) without releasing the mutex. This causes the other
97 // thread to wake up with WAIT_ABANDONED.
98 EXPECT_EQ(WAIT_ABANDONED, ::WaitForSingleObject(mutex_2_, INFINITE));
99 }
100
101 // Blocks until a mutex is acquired.
102 void WaitForMutexAcquired() {
103 EXPECT_EQ(WAIT_OBJECT_0,
104 ::WaitForSingleObject(mutex_acquired_event_.Get(), INFINITE));
105 }
106
107 // Signal the thread to acquire the second mutex.
108 void SignalToAcquireMutex() { EXPECT_TRUE(::SetEvent(wait_event_.Get())); }
109
110 // Terminates the thread.
111 bool Terminate() {
112 ScopedHandle thread_handle(::OpenThread(THREAD_TERMINATE, FALSE, tid()));
113 return ::TerminateThread(thread_handle.Get(), 0);
114 }
115
116 private:
117 ScopedHandle wait_event_;
118 ScopedHandle mutex_acquired_event_;
119
120 // The 2 mutex to acquire.
121 HANDLE mutex_1_;
122 HANDLE mutex_2_;
123
124 DISALLOW_COPY_AND_ASSIGN(DeadlockThread);
125 };
126
127 // Creates a thread that joins |thread_to_join| and then terminates when it
128 // finishes execution.
129 std::unique_ptr<SingleTaskThread> CreateJoiningThread(
130 SimpleThread* thread_to_join) {
131 std::unique_ptr<SingleTaskThread> thread(new SingleTaskThread(
132 Bind(&SimpleThread::Join, Unretained(thread_to_join))));
133 thread->Start();
134
135 return thread;
136 }
137
138 // Creates a thread that calls WaitForSingleObject() on the handle and then
139 // terminates when it unblocks.
140 std::unique_ptr<SingleTaskThread> CreateWaitingThread(HANDLE handle) {
141 std::unique_ptr<SingleTaskThread> thread(new SingleTaskThread(
142 Bind(IgnoreResult(&::WaitForSingleObject), handle, INFINITE)));
143 thread->Start();
144
145 return thread;
146 }
147
148 // Creates a thread that blocks on |mutex_2| after acquiring |mutex_1|.
149 std::unique_ptr<DeadlockThread> CreateDeadlockThread(HANDLE mutex_1,
150 HANDLE mutex_2) {
151 std::unique_ptr<DeadlockThread> thread(new DeadlockThread(mutex_1, mutex_2));
152 thread->Start();
153
154 // Wait until the first mutex is acquired before returning.
155 thread->WaitForMutexAcquired();
156
157 return thread;
158 }
159
160 // Child process to test the cross-process capability of the WCT api.
161 // This process will simulate a hang while holding a mutex that the parent
162 // process is waiting on.
163 MULTIPROCESS_TEST_MAIN(WaitChainTestProc) {
164 CommandLine* command_line = CommandLine::ForCurrentProcess();
165
166 ScopedHandle mutex = GetSwitchValueHandle(command_line, "mutex");
167 CHECK(mutex.IsValid());
168
169 ScopedHandle sync_event(GetSwitchValueHandle(command_line, "sync_event"));
170 CHECK(sync_event.IsValid());
171
172 // Acquire mutex.
173 CHECK(::WaitForSingleObject(mutex.Get(), INFINITE) == WAIT_OBJECT_0);
174
175 // Signal back to the parent process that the mutex is hold.
176 CHECK(::SetEvent(sync_event.Get()));
177
178 // Wait on a signal from the parent process before terminating.
179 CHECK(::WaitForSingleObject(sync_event.Get(), INFINITE) == WAIT_OBJECT_0);
180
181 return 0;
182 }
183
184 // Start a child process and passes the |mutex| and the |sync_event| to the
185 // command line.
186 Process StartChildProcess(HANDLE mutex, HANDLE sync_event) {
187 CommandLine command_line = GetMultiProcessTestChildBaseCommandLine();
188
189 AppendSwitchHandle(&command_line, "mutex", mutex);
190 AppendSwitchHandle(&command_line, "sync_event", sync_event);
191
192 LaunchOptions options;
193 HandlesToInheritVector handle_vector;
194 handle_vector.push_back(mutex);
195 handle_vector.push_back(sync_event);
196 options.handles_to_inherit = &handle_vector;
197 return SpawnMultiProcessTestChild("WaitChainTestProc", command_line, options);
198 }
199
200 // Returns true if the |wait_chain| is an alternating sequence of thread objects
201 // and synchronization objects.
202 bool WaitChainStructureIsCorrect(const WaitChainNodeVector& wait_chain) {
203 // Checks thread objects.
204 for (size_t i = 0; i < wait_chain.size(); i += 2) {
205 if (wait_chain[i].ObjectType != WctThreadType)
206 return false;
207 }
208
209 // Check synchronization objects.
210 for (size_t i = 1; i < wait_chain.size(); i += 2) {
211 if (wait_chain[i].ObjectType == WctThreadType)
212 return false;
213 }
214 return true;
215 }
216
217 // Returns true if the |wait_chain| goes through more than 1 process.
218 bool WaitChainIsCrossProcess(const WaitChainNodeVector& wait_chain) {
219 if (wait_chain.size() == 0)
220 return false;
221
222 // Just check that the process id changes somewhere in the chain.
223 // Note: ThreadObjects are every 2 nodes.
224 DWORD first_process = wait_chain[0].ThreadObject.ProcessId;
225 for (size_t i = 2; i < wait_chain.size(); i += 2) {
226 if (first_process != wait_chain[i].ThreadObject.ProcessId)
227 return true;
228 }
229 return false;
230 }
231
232 } // namespace
233
234 // Creates 2 threads that acquire their designated mutex and then try to
235 // acquire each others' mutex to cause a deadlock.
236 TEST(WaitChainTest, Deadlock) {
237 // 2 mutexes are needed to get a deadlock.
238 ScopedHandle mutex_1 = CreateMutex(false);
239 ASSERT_TRUE(mutex_1.IsValid());
240 ScopedHandle mutex_2 = CreateMutex(false);
241 ASSERT_TRUE(mutex_2.IsValid());
242
243 std::unique_ptr<DeadlockThread> deadlock_thread_1 =
244 CreateDeadlockThread(mutex_1.Get(), mutex_2.Get());
245 std::unique_ptr<DeadlockThread> deadlock_thread_2 =
246 CreateDeadlockThread(mutex_2.Get(), mutex_1.Get());
247
248 // Signal the threads to try to acquire the other mutex.
249 deadlock_thread_1->SignalToAcquireMutex();
250 deadlock_thread_2->SignalToAcquireMutex();
251 // Sleep to make sure the 2 threads got a chance to execute.
252 Sleep(10);
253
254 // Create a few waiting threads to get a longer wait chain.
255 std::unique_ptr<SingleTaskThread> waiting_thread_1 =
256 CreateJoiningThread(deadlock_thread_1.get());
257 std::unique_ptr<SingleTaskThread> waiting_thread_2 =
258 CreateJoiningThread(waiting_thread_1.get());
259
260 WaitChainNodeVector wait_chain;
261 bool is_deadlock;
262 ASSERT_TRUE(
263 GetThreadWaitChain(waiting_thread_2->tid(), &wait_chain, &is_deadlock));
264
265 EXPECT_EQ(9U, wait_chain.size());
266 EXPECT_TRUE(is_deadlock);
267 EXPECT_TRUE(WaitChainStructureIsCorrect(wait_chain));
268 EXPECT_FALSE(WaitChainIsCrossProcess(wait_chain));
269
270 ASSERT_TRUE(deadlock_thread_1->Terminate());
271
272 // The SimpleThread API expect Join() to be called before destruction.
273 deadlock_thread_2->Join();
274 waiting_thread_2->Join();
275 }
276
277 // Creates a child process that acquires a mutex and then blocks. A chain of
278 // threads then blocks on that mutex.
279 TEST(WaitChainTest, CrossProcess) {
280 ScopedHandle mutex = CreateMutex(true);
281 ASSERT_TRUE(mutex.IsValid());
282 ScopedHandle sync_event = CreateEvent(true);
283 ASSERT_TRUE(sync_event.IsValid());
284
285 Process child_process = StartChildProcess(mutex.Get(), sync_event.Get());
286 ASSERT_TRUE(child_process.IsValid());
287
288 // Wait for the child process to signal when it's holding the mutex.
289 EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(sync_event.Get(), INFINITE));
290
291 // Create a few waiting threads to get a longer wait chain.
292 std::unique_ptr<SingleTaskThread> waiting_thread_1 =
293 CreateWaitingThread(mutex.Get());
294 std::unique_ptr<SingleTaskThread> waiting_thread_2 =
295 CreateJoiningThread(waiting_thread_1.get());
296 std::unique_ptr<SingleTaskThread> waiting_thread_3 =
297 CreateJoiningThread(waiting_thread_2.get());
298
299 WaitChainNodeVector wait_chain;
300 bool is_deadlock;
301 ASSERT_TRUE(
302 GetThreadWaitChain(waiting_thread_3->tid(), &wait_chain, &is_deadlock));
303
304 EXPECT_EQ(7U, wait_chain.size());
305 EXPECT_FALSE(is_deadlock);
306 EXPECT_TRUE(WaitChainStructureIsCorrect(wait_chain));
307 EXPECT_TRUE(WaitChainIsCrossProcess(wait_chain));
308
309 // Unblock child process and wait for it to terminate.
310 ASSERT_TRUE(::SetEvent(sync_event.Get()));
311 ASSERT_TRUE(child_process.WaitForExit(nullptr));
312
313 // The SimpleThread API expect Join() to be called before destruction.
314 waiting_thread_3->Join();
315 }
316
317 } // namespace win
318 } // namespace base
OLDNEW
« no previous file with comments | « base/win/wait_chain.cc ('k') | chrome/browser/process_singleton_win.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698