blob: ceeff0f55dfafb98336d8b48b5e9554f69fe23f4 [file] [log] [blame]
Avi Drissman4a8573c2022-09-09 19:35:541// Copyright 2015 The Chromium Authors
siggi2a0214b2015-03-12 14:50:272// 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>
Jan Wilken Dörriead587c32021-03-11 14:09:2710#include <string>
dcheng4af48582016-04-19 00:29:3511
siggi2a0214b2015-03-12 14:50:2712#include "base/bind.h"
Hans Wennborg1790e6b2020-04-24 19:10:3313#include "base/check.h"
siggi2a0214b2015-03-12 14:50:2714#include "base/command_line.h"
15#include "base/compiler_specific.h"
16#include "base/files/file_path.h"
17#include "base/files/scoped_temp_dir.h"
Hans Wennborg1790e6b2020-04-24 19:10:3318#include "base/notreached.h"
siggi2a0214b2015-03-12 14:50:2719#include "base/process/launch.h"
20#include "base/process/process.h"
21#include "base/process/process_handle.h"
siggi2a0214b2015-03-12 14:50:2722#include "base/strings/stringprintf.h"
Devlin Cronin626d80c2018-06-01 01:08:3623#include "base/test/metrics/histogram_tester.h"
siggi2a0214b2015-03-12 14:50:2724#include "base/test/multiprocess_test.h"
Gabriel Charetted87f10f2022-03-31 00:44:2225#include "base/time/time.h"
siggi2a0214b2015-03-12 14:50:2726#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) {}
Peter Boström53c6c5952021-09-17 09:41:2658
59 ScopedVisibleWindow(const ScopedVisibleWindow&) = delete;
60 ScopedVisibleWindow& operator=(const ScopedVisibleWindow&) = delete;
61
siggi2a0214b2015-03-12 14:50:2762 ~ScopedVisibleWindow() {
63 if (window_)
64 ::DestroyWindow(window_);
65 if (class_)
66 ::UnregisterClass(reinterpret_cast<LPCWSTR>(class_), NULL);
67 }
68
69 bool Create() {
70 WNDCLASSEX wnd_cls = {0};
71 base::win::InitializeWindowClass(
72 L"ProcessSingletonTest", base::win::WrappedWindowProc<::DefWindowProc>,
73 0, // style
74 0, // class_extra
75 0, // window_extra
76 NULL, // cursor
77 NULL, // background
siggi42b2e642015-03-13 20:04:4378 NULL, // menu_name
siggi2a0214b2015-03-12 14:50:2779 NULL, // large_icon
80 NULL, // small_icon
81 &wnd_cls);
82
83 class_ = ::RegisterClassEx(&wnd_cls);
84 if (!class_)
85 return false;
86 window_ = ::CreateWindow(reinterpret_cast<LPCWSTR>(class_), 0, WS_POPUP, 0,
87 0, 0, 0, 0, 0, NULL, 0);
88 if (!window_)
89 return false;
90 ::ShowWindow(window_, SW_SHOW);
91
92 DCHECK(window_);
93 return true;
94 }
95
96 private:
97 ATOM class_;
98 HWND window_;
siggi2a0214b2015-03-12 14:50:2799};
100
101MULTIPROCESS_TEST_MAIN(ProcessSingletonTestProcessMain) {
102 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
103 base::FilePath user_data_dir =
104 cmd_line->GetSwitchValuePath(switches::kUserDataDir);
105 if (user_data_dir.empty())
106 return kErrorResultCode;
107
Peter Kasting690f8c82021-02-13 18:09:54108 std::wstring ready_event_name =
siggi2a0214b2015-03-12 14:50:27109 cmd_line->GetSwitchValueNative(kReadyEventNameFlag);
110
111 base::win::ScopedHandle ready_event(
112 ::OpenEvent(EVENT_MODIFY_STATE, FALSE, ready_event_name.c_str()));
113 if (!ready_event.IsValid())
114 return kErrorResultCode;
115
Peter Kasting690f8c82021-02-13 18:09:54116 std::wstring continue_event_name =
siggi2a0214b2015-03-12 14:50:27117 cmd_line->GetSwitchValueNative(kContinueEventNameFlag);
118
119 base::win::ScopedHandle continue_event(
120 ::OpenEvent(SYNCHRONIZE, FALSE, continue_event_name.c_str()));
121 if (!continue_event.IsValid())
122 return kErrorResultCode;
123
124 ScopedVisibleWindow visible_window;
125 if (cmd_line->HasSwitch(kCreateWindowFlag)) {
126 if (!visible_window.Create())
127 return kErrorResultCode;
128 }
129
130 // Instantiate the process singleton.
Alexander Cooper4bcb0ce2020-07-16 23:10:38131 ProcessSingleton process_singleton(
132 user_data_dir, base::BindRepeating(&NotificationCallback));
siggi2a0214b2015-03-12 14:50:27133
134 if (!process_singleton.Create())
135 return kErrorResultCode;
136
137 // Signal ready and block for the continue event.
138 if (!::SetEvent(ready_event.Get()))
139 return kErrorResultCode;
140
141 if (::WaitForSingleObject(continue_event.Get(), INFINITE) != WAIT_OBJECT_0)
142 return kErrorResultCode;
143
144 return 0;
145}
146
147// This fixture is for testing the Windows platform-specific failure modes
148// of rendezvous, specifically the ones where the singleton-owning process
149// is hung.
150class ProcessSingletonTest : public base::MultiProcessTest {
Peter Boströmfadb1752021-09-30 19:17:01151 public:
152 ProcessSingletonTest(const ProcessSingletonTest&) = delete;
153 ProcessSingletonTest& operator=(const ProcessSingletonTest&) = delete;
154
siggi2a0214b2015-03-12 14:50:27155 protected:
156 enum WindowOption { WITH_WINDOW, NO_WINDOW };
157
158 ProcessSingletonTest()
159 : window_option_(NO_WINDOW), should_kill_called_(false) {}
160
161 void SetUp() override {
162 ASSERT_NO_FATAL_FAILURE(base::MultiProcessTest::SetUp());
163
164 // Drop the process finder notification timeout to one second for testing.
Peter Kastinge5a38ed2021-10-02 03:06:35165 old_notification_timeout_ =
166 chrome::SetNotificationTimeoutForTesting(base::Seconds(1));
siggi2a0214b2015-03-12 14:50:27167 }
168
169 void TearDown() override {
170 chrome::SetNotificationTimeoutForTesting(old_notification_timeout_);
171
Jay Civelli4a44260b2017-08-21 19:26:29172 if (browser_victim_.IsValid()) {
siggi2a0214b2015-03-12 14:50:27173 EXPECT_TRUE(::SetEvent(continue_event_.Get()));
Jay Civelli4a44260b2017-08-21 19:26:29174 EXPECT_TRUE(browser_victim_.WaitForExit(nullptr));
siggi2a0214b2015-03-12 14:50:27175 }
176
177 base::MultiProcessTest::TearDown();
178 }
179
180 void LaunchHungBrowserProcess(WindowOption window_option) {
181 // Create a unique user data dir to rendezvous on.
182 ASSERT_TRUE(user_data_dir_.CreateUniqueTempDir());
183
184 // Create the named "ready" event, this is unique to our process.
185 ready_event_name_ =
186 base::StringPrintf(L"ready-event-%d", base::GetCurrentProcId());
187 base::win::ScopedHandle ready_event(
188 ::CreateEvent(NULL, TRUE, FALSE, ready_event_name_.c_str()));
189 ASSERT_TRUE(ready_event.IsValid());
190
191 // Create the named "continue" event, this is unique to our process.
192 continue_event_name_ =
193 base::StringPrintf(L"continue-event-%d", base::GetCurrentProcId());
194 continue_event_.Set(
195 ::CreateEvent(NULL, TRUE, FALSE, continue_event_name_.c_str()));
196 ASSERT_TRUE(continue_event_.IsValid());
197
198 window_option_ = window_option;
199
200 base::LaunchOptions options;
201 options.start_hidden = true;
202 browser_victim_ =
203 SpawnChildWithOptions("ProcessSingletonTestProcessMain", options);
204
205 // Wait for the ready event (or process exit).
Jay Civelli4a44260b2017-08-21 19:26:29206 HANDLE handles[] = {ready_event.Get(), browser_victim_.Handle()};
siggi2a0214b2015-03-12 14:50:27207 // The wait should always return because either |ready_event| is signaled or
208 // |browser_victim_| died unexpectedly or exited on error.
209 DWORD result =
Daniel Cheng7d9e3d52022-02-26 09:03:24210 ::WaitForMultipleObjects(std::size(handles), handles, FALSE, INFINITE);
siggi2a0214b2015-03-12 14:50:27211 ASSERT_EQ(WAIT_OBJECT_0, result);
212 }
213
214 base::CommandLine MakeCmdLine(const std::string& procname) override {
215 base::CommandLine cmd_line = base::MultiProcessTest::MakeCmdLine(procname);
216
vabr8023d872016-09-15 08:12:22217 cmd_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir_.GetPath());
siggi2a0214b2015-03-12 14:50:27218 cmd_line.AppendSwitchNative(kReadyEventNameFlag, ready_event_name_);
219 cmd_line.AppendSwitchNative(kContinueEventNameFlag, continue_event_name_);
220 if (window_option_ == WITH_WINDOW)
221 cmd_line.AppendSwitch(kCreateWindowFlag);
222
223 return cmd_line;
224 }
225
226 void PrepareTest(WindowOption window_option, bool allow_kill) {
227 ASSERT_NO_FATAL_FAILURE(LaunchHungBrowserProcess(window_option));
228
229 // The ready event has been signalled - the process singleton is held by
230 // the hung sub process.
Peter Boström560859d2021-05-01 01:31:25231 test_singleton_ = std::make_unique<ProcessSingleton>(
232 user_data_dir(), base::BindRepeating(&NotificationCallback));
siggi2a0214b2015-03-12 14:50:27233
234 test_singleton_->OverrideShouldKillRemoteProcessCallbackForTesting(
Alexander Cooper4bcb0ce2020-07-16 23:10:38235 base::BindRepeating(&ProcessSingletonTest::MockShouldKillRemoteProcess,
236 base::Unretained(this), allow_kill));
siggi2a0214b2015-03-12 14:50:27237 }
238
Jay Civelli4a44260b2017-08-21 19:26:29239 base::Process* browser_victim() { return &browser_victim_; }
vabr8023d872016-09-15 08:12:22240 const base::FilePath& user_data_dir() const {
241 return user_data_dir_.GetPath();
242 }
siggi2a0214b2015-03-12 14:50:27243 ProcessSingleton* test_singleton() const { return test_singleton_.get(); }
244 bool should_kill_called() const { return should_kill_called_; }
245
aseren028ea152017-05-16 17:22:43246 const base::HistogramTester& histogram_tester() const {
247 return histogram_tester_;
248 }
249
siggi2a0214b2015-03-12 14:50:27250 private:
251 bool MockShouldKillRemoteProcess(bool allow_kill) {
252 should_kill_called_ = true;
253 return allow_kill;
254 }
255
Peter Kasting690f8c82021-02-13 18:09:54256 std::wstring ready_event_name_;
257 std::wstring continue_event_name_;
siggi2a0214b2015-03-12 14:50:27258
259 WindowOption window_option_;
260 base::ScopedTempDir user_data_dir_;
Jay Civelli4a44260b2017-08-21 19:26:29261 base::Process browser_victim_;
siggi2a0214b2015-03-12 14:50:27262 base::win::ScopedHandle continue_event_;
263
dcheng4af48582016-04-19 00:29:35264 std::unique_ptr<ProcessSingleton> test_singleton_;
siggi2a0214b2015-03-12 14:50:27265
266 base::TimeDelta old_notification_timeout_;
267 bool should_kill_called_;
aseren028ea152017-05-16 17:22:43268 base::HistogramTester histogram_tester_;
siggi2a0214b2015-03-12 14:50:27269};
270
271} // namespace
272
273TEST_F(ProcessSingletonTest, KillsHungBrowserWithNoWindows) {
274 ASSERT_NO_FATAL_FAILURE(PrepareTest(NO_WINDOW, false));
275
276 // As the hung browser has no visible window, it'll be killed without
277 // user interaction.
278 ProcessSingleton::NotifyResult notify_result =
279 test_singleton()->NotifyOtherProcessOrCreate();
gcomanici66815672016-08-30 04:54:10280
281 // The hung process was killed and the notification is equivalent to
282 // a non existent process.
283 ASSERT_EQ(ProcessSingleton::PROCESS_NONE, notify_result);
siggi2a0214b2015-03-12 14:50:27284
285 // The should-kill callback should not have been called, as the "browser" does
286 // not have visible window.
287 EXPECT_FALSE(should_kill_called());
288
aseren028ea152017-05-16 17:22:43289 histogram_tester().ExpectUniqueSample(
290 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
291 ProcessSingleton::TERMINATE_SUCCEEDED, 1u);
292 histogram_tester().ExpectTotalCount(
293 "Chrome.ProcessSingleton.TerminateProcessTime", 1u);
294 histogram_tester().ExpectUniqueSample(
Aleksei Seren75591e732017-12-01 21:47:45295 "Chrome.ProcessSingleton.TerminateProcessErrorCode.Windows", 0, 1u);
aseren028ea152017-05-16 17:22:43296 histogram_tester().ExpectUniqueSample(
297 "Chrome.ProcessSingleton.TerminationWaitErrorCode.Windows", 0, 1u);
298 histogram_tester().ExpectUniqueSample(
299 "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason",
300 ProcessSingleton::NO_VISIBLE_WINDOW_FOUND, 1u);
301
gcomanici66815672016-08-30 04:54:10302 // Verify that the hung browser has been terminated with the
siggi2a0214b2015-03-12 14:50:27303 // RESULT_CODE_HUNG exit code.
304 int exit_code = 0;
305 EXPECT_TRUE(
306 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
307 EXPECT_EQ(content::RESULT_CODE_HUNG, exit_code);
308}
309
310TEST_F(ProcessSingletonTest, DoesntKillWithoutUserPermission) {
311 ASSERT_NO_FATAL_FAILURE(PrepareTest(WITH_WINDOW, false));
312
313 // As the hung browser has a visible window, this should query the user
314 // before killing the hung process.
315 ProcessSingleton::NotifyResult notify_result =
316 test_singleton()->NotifyOtherProcessOrCreate();
317 ASSERT_EQ(ProcessSingleton::PROCESS_NOTIFIED, notify_result);
318
319 // The should-kill callback should have been called, as the "browser" has a
320 // visible window.
321 EXPECT_TRUE(should_kill_called());
322
Mikhail Atuchina6e8bcd32018-07-06 12:14:03323 histogram_tester().ExpectUniqueSample(
324 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
325 ProcessSingleton::USER_REFUSED_TERMINATION, 1u);
aseren028ea152017-05-16 17:22:43326
siggi2a0214b2015-03-12 14:50:27327 // Make sure the process hasn't been killed.
328 int exit_code = 0;
329 EXPECT_FALSE(
330 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
331}
332
333TEST_F(ProcessSingletonTest, KillWithUserPermission) {
334 ASSERT_NO_FATAL_FAILURE(PrepareTest(WITH_WINDOW, true));
335
336 // As the hung browser has a visible window, this should query the user
337 // before killing the hung process.
338 ProcessSingleton::NotifyResult notify_result =
339 test_singleton()->NotifyOtherProcessOrCreate();
gcomanici66815672016-08-30 04:54:10340
341 // The hung process was killed and the notification is equivalent to
342 // a non existent process.
343 ASSERT_EQ(ProcessSingleton::PROCESS_NONE, notify_result);
siggi2a0214b2015-03-12 14:50:27344
345 // The should-kill callback should have been called, as the "browser" has a
346 // visible window.
347 EXPECT_TRUE(should_kill_called());
348
aseren028ea152017-05-16 17:22:43349 histogram_tester().ExpectUniqueSample(
350 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
351 ProcessSingleton::TERMINATE_SUCCEEDED, 1u);
352 histogram_tester().ExpectTotalCount(
353 "Chrome.ProcessSingleton.TerminateProcessTime", 1u);
354 histogram_tester().ExpectUniqueSample(
Aleksei Seren75591e732017-12-01 21:47:45355 "Chrome.ProcessSingleton.TerminateProcessErrorCode.Windows", 0, 1u);
aseren028ea152017-05-16 17:22:43356 histogram_tester().ExpectUniqueSample(
357 "Chrome.ProcessSingleton.TerminationWaitErrorCode.Windows", 0, 1u);
358 histogram_tester().ExpectUniqueSample(
359 "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason",
360 ProcessSingleton::USER_ACCEPTED_TERMINATION, 1u);
361
gcomanici66815672016-08-30 04:54:10362 // Verify that the hung browser has been terminated with the
siggi2a0214b2015-03-12 14:50:27363 // RESULT_CODE_HUNG exit code.
364 int exit_code = 0;
365 EXPECT_TRUE(
366 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
367 EXPECT_EQ(content::RESULT_CODE_HUNG, exit_code);
368}