blob: cf5607f7b241b206ca7bc4371b1460bbff988484 [file] [log] [blame]
siggi2a0214b2015-03-12 14:50:271// Copyright 2015 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 "chrome/browser/process_singleton.h"
6
7#include <windows.h>
8
dcheng4af48582016-04-19 00:29:359#include <memory>
10
siggi2a0214b2015-03-12 14:50:2711#include "base/bind.h"
Hans Wennborg1790e6b2020-04-24 19:10:3312#include "base/check.h"
siggi2a0214b2015-03-12 14:50:2713#include "base/command_line.h"
14#include "base/compiler_specific.h"
15#include "base/files/file_path.h"
16#include "base/files/scoped_temp_dir.h"
Hans Wennborg1790e6b2020-04-24 19:10:3317#include "base/notreached.h"
siggi2a0214b2015-03-12 14:50:2718#include "base/process/launch.h"
19#include "base/process/process.h"
20#include "base/process/process_handle.h"
Avi Drissman5f0fb8c2018-12-25 23:20:4921#include "base/stl_util.h"
siggi2a0214b2015-03-12 14:50:2722#include "base/strings/string16.h"
23#include "base/strings/stringprintf.h"
Devlin Cronin626d80c2018-06-01 01:08:3624#include "base/test/metrics/histogram_tester.h"
siggi2a0214b2015-03-12 14:50:2725#include "base/test/multiprocess_test.h"
26#include "base/win/scoped_handle.h"
27#include "base/win/wrapped_window_proc.h"
pmonette23c8fb7e2016-06-27 20:45:1128#include "chrome/browser/win/chrome_process_finder.h"
siggi2a0214b2015-03-12 14:50:2729#include "chrome/common/chrome_constants.h"
30#include "chrome/common/chrome_switches.h"
31#include "content/public/common/result_codes.h"
32#include "testing/gtest/include/gtest/gtest.h"
33#include "testing/multiprocess_func_list.h"
34
35namespace {
36
37const char kReadyEventNameFlag[] = "ready_event_name";
38const char kContinueEventNameFlag[] = "continue_event_name";
39const char kCreateWindowFlag[] = "create_window";
40const int kErrorResultCode = 0x345;
41
42bool NotificationCallback(const base::CommandLine& command_line,
43 const base::FilePath& current_directory) {
44 // This is never called in this test, but would signal that the singleton
45 // notification was successfully handled.
46 NOTREACHED();
47 return true;
48}
49
50// The ProcessSingleton kills hung browsers with no visible windows without user
51// interaction. If a hung browser has visible UI, however, it asks the user
52// first.
53// This class is the very minimal implementation to create a visible window
54// in the hung test process to allow testing the latter path.
55class ScopedVisibleWindow {
56 public:
57 ScopedVisibleWindow() : class_(0), window_(NULL) {}
58 ~ScopedVisibleWindow() {
59 if (window_)
60 ::DestroyWindow(window_);
61 if (class_)
62 ::UnregisterClass(reinterpret_cast<LPCWSTR>(class_), NULL);
63 }
64
65 bool Create() {
66 WNDCLASSEX wnd_cls = {0};
67 base::win::InitializeWindowClass(
68 L"ProcessSingletonTest", base::win::WrappedWindowProc<::DefWindowProc>,
69 0, // style
70 0, // class_extra
71 0, // window_extra
72 NULL, // cursor
73 NULL, // background
siggi42b2e642015-03-13 20:04:4374 NULL, // menu_name
siggi2a0214b2015-03-12 14:50:2775 NULL, // large_icon
76 NULL, // small_icon
77 &wnd_cls);
78
79 class_ = ::RegisterClassEx(&wnd_cls);
80 if (!class_)
81 return false;
82 window_ = ::CreateWindow(reinterpret_cast<LPCWSTR>(class_), 0, WS_POPUP, 0,
83 0, 0, 0, 0, 0, NULL, 0);
84 if (!window_)
85 return false;
86 ::ShowWindow(window_, SW_SHOW);
87
88 DCHECK(window_);
89 return true;
90 }
91
92 private:
93 ATOM class_;
94 HWND window_;
95
96 DISALLOW_COPY_AND_ASSIGN(ScopedVisibleWindow);
97};
98
99MULTIPROCESS_TEST_MAIN(ProcessSingletonTestProcessMain) {
100 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
101 base::FilePath user_data_dir =
102 cmd_line->GetSwitchValuePath(switches::kUserDataDir);
103 if (user_data_dir.empty())
104 return kErrorResultCode;
105
Peter Kasting690f8c82021-02-13 18:09:54106 std::wstring ready_event_name =
siggi2a0214b2015-03-12 14:50:27107 cmd_line->GetSwitchValueNative(kReadyEventNameFlag);
108
109 base::win::ScopedHandle ready_event(
110 ::OpenEvent(EVENT_MODIFY_STATE, FALSE, ready_event_name.c_str()));
111 if (!ready_event.IsValid())
112 return kErrorResultCode;
113
Peter Kasting690f8c82021-02-13 18:09:54114 std::wstring continue_event_name =
siggi2a0214b2015-03-12 14:50:27115 cmd_line->GetSwitchValueNative(kContinueEventNameFlag);
116
117 base::win::ScopedHandle continue_event(
118 ::OpenEvent(SYNCHRONIZE, FALSE, continue_event_name.c_str()));
119 if (!continue_event.IsValid())
120 return kErrorResultCode;
121
122 ScopedVisibleWindow visible_window;
123 if (cmd_line->HasSwitch(kCreateWindowFlag)) {
124 if (!visible_window.Create())
125 return kErrorResultCode;
126 }
127
128 // Instantiate the process singleton.
Alexander Cooper4bcb0ce2020-07-16 23:10:38129 ProcessSingleton process_singleton(
130 user_data_dir, base::BindRepeating(&NotificationCallback));
siggi2a0214b2015-03-12 14:50:27131
132 if (!process_singleton.Create())
133 return kErrorResultCode;
134
135 // Signal ready and block for the continue event.
136 if (!::SetEvent(ready_event.Get()))
137 return kErrorResultCode;
138
139 if (::WaitForSingleObject(continue_event.Get(), INFINITE) != WAIT_OBJECT_0)
140 return kErrorResultCode;
141
142 return 0;
143}
144
145// This fixture is for testing the Windows platform-specific failure modes
146// of rendezvous, specifically the ones where the singleton-owning process
147// is hung.
148class ProcessSingletonTest : public base::MultiProcessTest {
149 protected:
150 enum WindowOption { WITH_WINDOW, NO_WINDOW };
151
152 ProcessSingletonTest()
153 : window_option_(NO_WINDOW), should_kill_called_(false) {}
154
155 void SetUp() override {
156 ASSERT_NO_FATAL_FAILURE(base::MultiProcessTest::SetUp());
157
158 // Drop the process finder notification timeout to one second for testing.
159 old_notification_timeout_ = chrome::SetNotificationTimeoutForTesting(
160 base::TimeDelta::FromSeconds(1));
161 }
162
163 void TearDown() override {
164 chrome::SetNotificationTimeoutForTesting(old_notification_timeout_);
165
Jay Civelli4a44260b2017-08-21 19:26:29166 if (browser_victim_.IsValid()) {
siggi2a0214b2015-03-12 14:50:27167 EXPECT_TRUE(::SetEvent(continue_event_.Get()));
Jay Civelli4a44260b2017-08-21 19:26:29168 EXPECT_TRUE(browser_victim_.WaitForExit(nullptr));
siggi2a0214b2015-03-12 14:50:27169 }
170
171 base::MultiProcessTest::TearDown();
172 }
173
174 void LaunchHungBrowserProcess(WindowOption window_option) {
175 // Create a unique user data dir to rendezvous on.
176 ASSERT_TRUE(user_data_dir_.CreateUniqueTempDir());
177
178 // Create the named "ready" event, this is unique to our process.
179 ready_event_name_ =
180 base::StringPrintf(L"ready-event-%d", base::GetCurrentProcId());
181 base::win::ScopedHandle ready_event(
182 ::CreateEvent(NULL, TRUE, FALSE, ready_event_name_.c_str()));
183 ASSERT_TRUE(ready_event.IsValid());
184
185 // Create the named "continue" event, this is unique to our process.
186 continue_event_name_ =
187 base::StringPrintf(L"continue-event-%d", base::GetCurrentProcId());
188 continue_event_.Set(
189 ::CreateEvent(NULL, TRUE, FALSE, continue_event_name_.c_str()));
190 ASSERT_TRUE(continue_event_.IsValid());
191
192 window_option_ = window_option;
193
194 base::LaunchOptions options;
195 options.start_hidden = true;
196 browser_victim_ =
197 SpawnChildWithOptions("ProcessSingletonTestProcessMain", options);
198
199 // Wait for the ready event (or process exit).
Jay Civelli4a44260b2017-08-21 19:26:29200 HANDLE handles[] = {ready_event.Get(), browser_victim_.Handle()};
siggi2a0214b2015-03-12 14:50:27201 // The wait should always return because either |ready_event| is signaled or
202 // |browser_victim_| died unexpectedly or exited on error.
203 DWORD result =
Avi Drissman5f0fb8c2018-12-25 23:20:49204 ::WaitForMultipleObjects(base::size(handles), handles, FALSE, INFINITE);
siggi2a0214b2015-03-12 14:50:27205 ASSERT_EQ(WAIT_OBJECT_0, result);
206 }
207
208 base::CommandLine MakeCmdLine(const std::string& procname) override {
209 base::CommandLine cmd_line = base::MultiProcessTest::MakeCmdLine(procname);
210
vabr8023d872016-09-15 08:12:22211 cmd_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir_.GetPath());
siggi2a0214b2015-03-12 14:50:27212 cmd_line.AppendSwitchNative(kReadyEventNameFlag, ready_event_name_);
213 cmd_line.AppendSwitchNative(kContinueEventNameFlag, continue_event_name_);
214 if (window_option_ == WITH_WINDOW)
215 cmd_line.AppendSwitch(kCreateWindowFlag);
216
217 return cmd_line;
218 }
219
220 void PrepareTest(WindowOption window_option, bool allow_kill) {
221 ASSERT_NO_FATAL_FAILURE(LaunchHungBrowserProcess(window_option));
222
223 // The ready event has been signalled - the process singleton is held by
224 // the hung sub process.
225 test_singleton_.reset(new ProcessSingleton(
Alexander Cooper4bcb0ce2020-07-16 23:10:38226 user_data_dir(), base::BindRepeating(&NotificationCallback)));
siggi2a0214b2015-03-12 14:50:27227
228 test_singleton_->OverrideShouldKillRemoteProcessCallbackForTesting(
Alexander Cooper4bcb0ce2020-07-16 23:10:38229 base::BindRepeating(&ProcessSingletonTest::MockShouldKillRemoteProcess,
230 base::Unretained(this), allow_kill));
siggi2a0214b2015-03-12 14:50:27231 }
232
Jay Civelli4a44260b2017-08-21 19:26:29233 base::Process* browser_victim() { return &browser_victim_; }
vabr8023d872016-09-15 08:12:22234 const base::FilePath& user_data_dir() const {
235 return user_data_dir_.GetPath();
236 }
siggi2a0214b2015-03-12 14:50:27237 ProcessSingleton* test_singleton() const { return test_singleton_.get(); }
238 bool should_kill_called() const { return should_kill_called_; }
239
aseren028ea152017-05-16 17:22:43240 const base::HistogramTester& histogram_tester() const {
241 return histogram_tester_;
242 }
243
siggi2a0214b2015-03-12 14:50:27244 private:
245 bool MockShouldKillRemoteProcess(bool allow_kill) {
246 should_kill_called_ = true;
247 return allow_kill;
248 }
249
Peter Kasting690f8c82021-02-13 18:09:54250 std::wstring ready_event_name_;
251 std::wstring continue_event_name_;
siggi2a0214b2015-03-12 14:50:27252
253 WindowOption window_option_;
254 base::ScopedTempDir user_data_dir_;
Jay Civelli4a44260b2017-08-21 19:26:29255 base::Process browser_victim_;
siggi2a0214b2015-03-12 14:50:27256 base::win::ScopedHandle continue_event_;
257
dcheng4af48582016-04-19 00:29:35258 std::unique_ptr<ProcessSingleton> test_singleton_;
siggi2a0214b2015-03-12 14:50:27259
260 base::TimeDelta old_notification_timeout_;
261 bool should_kill_called_;
aseren028ea152017-05-16 17:22:43262 base::HistogramTester histogram_tester_;
siggi2a0214b2015-03-12 14:50:27263
264 DISALLOW_COPY_AND_ASSIGN(ProcessSingletonTest);
265};
266
267} // namespace
268
269TEST_F(ProcessSingletonTest, KillsHungBrowserWithNoWindows) {
270 ASSERT_NO_FATAL_FAILURE(PrepareTest(NO_WINDOW, false));
271
272 // As the hung browser has no visible window, it'll be killed without
273 // user interaction.
274 ProcessSingleton::NotifyResult notify_result =
275 test_singleton()->NotifyOtherProcessOrCreate();
gcomanici66815672016-08-30 04:54:10276
277 // The hung process was killed and the notification is equivalent to
278 // a non existent process.
279 ASSERT_EQ(ProcessSingleton::PROCESS_NONE, notify_result);
siggi2a0214b2015-03-12 14:50:27280
281 // The should-kill callback should not have been called, as the "browser" does
282 // not have visible window.
283 EXPECT_FALSE(should_kill_called());
284
aseren028ea152017-05-16 17:22:43285 histogram_tester().ExpectUniqueSample(
286 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
287 ProcessSingleton::TERMINATE_SUCCEEDED, 1u);
288 histogram_tester().ExpectTotalCount(
289 "Chrome.ProcessSingleton.TerminateProcessTime", 1u);
290 histogram_tester().ExpectUniqueSample(
Aleksei Seren75591e732017-12-01 21:47:45291 "Chrome.ProcessSingleton.TerminateProcessErrorCode.Windows", 0, 1u);
aseren028ea152017-05-16 17:22:43292 histogram_tester().ExpectUniqueSample(
293 "Chrome.ProcessSingleton.TerminationWaitErrorCode.Windows", 0, 1u);
294 histogram_tester().ExpectUniqueSample(
295 "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason",
296 ProcessSingleton::NO_VISIBLE_WINDOW_FOUND, 1u);
297
gcomanici66815672016-08-30 04:54:10298 // Verify that the hung browser has been terminated with the
siggi2a0214b2015-03-12 14:50:27299 // RESULT_CODE_HUNG exit code.
300 int exit_code = 0;
301 EXPECT_TRUE(
302 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
303 EXPECT_EQ(content::RESULT_CODE_HUNG, exit_code);
304}
305
306TEST_F(ProcessSingletonTest, DoesntKillWithoutUserPermission) {
307 ASSERT_NO_FATAL_FAILURE(PrepareTest(WITH_WINDOW, false));
308
309 // As the hung browser has a visible window, this should query the user
310 // before killing the hung process.
311 ProcessSingleton::NotifyResult notify_result =
312 test_singleton()->NotifyOtherProcessOrCreate();
313 ASSERT_EQ(ProcessSingleton::PROCESS_NOTIFIED, notify_result);
314
315 // The should-kill callback should have been called, as the "browser" has a
316 // visible window.
317 EXPECT_TRUE(should_kill_called());
318
Mikhail Atuchina6e8bcd32018-07-06 12:14:03319 histogram_tester().ExpectUniqueSample(
320 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
321 ProcessSingleton::USER_REFUSED_TERMINATION, 1u);
aseren028ea152017-05-16 17:22:43322
siggi2a0214b2015-03-12 14:50:27323 // Make sure the process hasn't been killed.
324 int exit_code = 0;
325 EXPECT_FALSE(
326 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
327}
328
329TEST_F(ProcessSingletonTest, KillWithUserPermission) {
330 ASSERT_NO_FATAL_FAILURE(PrepareTest(WITH_WINDOW, true));
331
332 // As the hung browser has a visible window, this should query the user
333 // before killing the hung process.
334 ProcessSingleton::NotifyResult notify_result =
335 test_singleton()->NotifyOtherProcessOrCreate();
gcomanici66815672016-08-30 04:54:10336
337 // The hung process was killed and the notification is equivalent to
338 // a non existent process.
339 ASSERT_EQ(ProcessSingleton::PROCESS_NONE, notify_result);
siggi2a0214b2015-03-12 14:50:27340
341 // The should-kill callback should have been called, as the "browser" has a
342 // visible window.
343 EXPECT_TRUE(should_kill_called());
344
aseren028ea152017-05-16 17:22:43345 histogram_tester().ExpectUniqueSample(
346 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
347 ProcessSingleton::TERMINATE_SUCCEEDED, 1u);
348 histogram_tester().ExpectTotalCount(
349 "Chrome.ProcessSingleton.TerminateProcessTime", 1u);
350 histogram_tester().ExpectUniqueSample(
Aleksei Seren75591e732017-12-01 21:47:45351 "Chrome.ProcessSingleton.TerminateProcessErrorCode.Windows", 0, 1u);
aseren028ea152017-05-16 17:22:43352 histogram_tester().ExpectUniqueSample(
353 "Chrome.ProcessSingleton.TerminationWaitErrorCode.Windows", 0, 1u);
354 histogram_tester().ExpectUniqueSample(
355 "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason",
356 ProcessSingleton::USER_ACCEPTED_TERMINATION, 1u);
357
gcomanici66815672016-08-30 04:54:10358 // Verify that the hung browser has been terminated with the
siggi2a0214b2015-03-12 14:50:27359 // RESULT_CODE_HUNG exit code.
360 int exit_code = 0;
361 EXPECT_TRUE(
362 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
363 EXPECT_EQ(content::RESULT_CODE_HUNG, exit_code);
364}