OLD | NEW |
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include <windows.h> | 5 #include <windows.h> |
6 | 6 |
7 #include "base/at_exit.h" | 7 #include "base/at_exit.h" |
| 8 #include "base/bind.h" |
| 9 #include "base/bind_helpers.h" |
8 #include "base/command_line.h" | 10 #include "base/command_line.h" |
9 #include "base/logging_win.h" | 11 #include "base/logging_win.h" |
| 12 #include "base/macros.h" |
| 13 #include "base/memory/ref_counted.h" |
| 14 #include "base/message_loop/message_loop.h" |
| 15 #include "base/message_loop/message_loop_proxy.h" |
10 #include "base/process/process.h" | 16 #include "base/process/process.h" |
| 17 #include "base/run_loop.h" |
| 18 #include "base/sequenced_task_runner.h" |
11 #include "base/synchronization/waitable_event.h" | 19 #include "base/synchronization/waitable_event.h" |
12 #include "base/template_util.h" | 20 #include "base/template_util.h" |
| 21 #include "base/threading/thread.h" |
| 22 #include "base/time/time.h" |
| 23 #include "components/browser_watcher/endsession_watcher_window_win.h" |
13 #include "components/browser_watcher/exit_code_watcher_win.h" | 24 #include "components/browser_watcher/exit_code_watcher_win.h" |
14 #include "components/browser_watcher/exit_funnel_win.h" | 25 #include "components/browser_watcher/exit_funnel_win.h" |
15 #include "components/browser_watcher/watcher_main_api_win.h" | 26 #include "components/browser_watcher/watcher_main_api_win.h" |
16 | 27 |
17 namespace { | 28 namespace { |
18 | 29 |
19 // Use the same log facility as Chrome for convenience. | 30 // Use the same log facility as Chrome for convenience. |
20 // {7FE69228-633E-4f06-80C1-527FEA23E3A7} | 31 // {7FE69228-633E-4f06-80C1-527FEA23E3A7} |
21 const GUID kChromeWatcherTraceProviderName = { | 32 const GUID kChromeWatcherTraceProviderName = { |
22 0x7fe69228, 0x633e, 0x4f06, | 33 0x7fe69228, 0x633e, 0x4f06, |
23 { 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7 } }; | 34 { 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7 } }; |
24 | 35 |
25 // The data shared from the main function to the console handler. | 36 // Takes care of monitoring a browser. This class watches for a browser's exit |
26 struct HandlerData { | 37 // code, as well as listening for WM_ENDSESSION messages. Events are recorded |
27 HandlerData() : handler_done(true /* manual_reset */, | 38 // in an exit funnel, for reporting the next time Chrome runs. |
28 false /* initially_signaled */) { | 39 class BrowserMonitor { |
| 40 public: |
| 41 BrowserMonitor(base::RunLoop* run_loop, const base::char16* registry_path); |
| 42 ~BrowserMonitor(); |
| 43 |
| 44 // Starts the monitor, returns true on success. |
| 45 bool StartWatching(const base::char16* registry_path, |
| 46 base::Process process); |
| 47 |
| 48 private: |
| 49 // Called from EndSessionWatcherWindow on a WM_ENDSESSION message. |
| 50 void OnEndSession(LPARAM lparam); |
| 51 |
| 52 // Blocking function that runs on |background_thread_|. |
| 53 void Watch(); |
| 54 |
| 55 // Posted to main thread from Watch when browser exits. |
| 56 void BrowserExited(); |
| 57 |
| 58 // True if BrowserExited has run. |
| 59 bool browser_exited_; |
| 60 |
| 61 // The funnel used to record events for this browser. |
| 62 browser_watcher::ExitFunnel exit_funnel_; |
| 63 |
| 64 browser_watcher::ExitCodeWatcher exit_code_watcher_; |
| 65 browser_watcher::EndSessionWatcherWindow end_session_watcher_window_; |
| 66 |
| 67 // The thread that runs Watch(). |
| 68 base::Thread background_thread_; |
| 69 |
| 70 // The run loop for the main thread and its task runner. |
| 71 base::RunLoop* run_loop_; |
| 72 scoped_refptr<base::SequencedTaskRunner> main_thread_; |
| 73 |
| 74 DISALLOW_COPY_AND_ASSIGN(BrowserMonitor); |
| 75 }; |
| 76 |
| 77 BrowserMonitor::BrowserMonitor(base::RunLoop* run_loop, |
| 78 const base::char16* registry_path) : |
| 79 browser_exited_(false), |
| 80 exit_code_watcher_(registry_path), |
| 81 end_session_watcher_window_( |
| 82 base::Bind(&BrowserMonitor::OnEndSession, base::Unretained(this))), |
| 83 background_thread_("BrowserWatcherThread"), |
| 84 run_loop_(run_loop), |
| 85 main_thread_(base::MessageLoopProxy::current()) { |
| 86 } |
| 87 |
| 88 BrowserMonitor::~BrowserMonitor() { |
| 89 } |
| 90 |
| 91 bool BrowserMonitor::StartWatching(const base::char16* registry_path, |
| 92 base::Process process) { |
| 93 if (!exit_code_watcher_.Initialize(process.Pass())) |
| 94 return false; |
| 95 |
| 96 if (!exit_funnel_.Init(registry_path, |
| 97 exit_code_watcher_.process().Handle())) { |
| 98 return false; |
29 } | 99 } |
30 | 100 |
31 base::WaitableEvent handler_done; | 101 if (!background_thread_.StartWithOptions( |
32 base::ProcessHandle process; | 102 base::Thread::Options(base::MessageLoop::TYPE_IO, 0))) { |
33 const base::char16* registry_path; | 103 return false; |
34 }; | |
35 | |
36 HandlerData *g_handler_data = nullptr; | |
37 | |
38 BOOL CALLBACK ConsoleCtrlHandler(DWORD ctl_type) { | |
39 if (g_handler_data && ctl_type == CTRL_LOGOFF_EVENT) { | |
40 // Record the watcher logoff event in the browser's exit funnel. | |
41 browser_watcher::ExitFunnel funnel; | |
42 funnel.Init(g_handler_data->registry_path, g_handler_data->process); | |
43 funnel.RecordEvent(L"WatcherLogoff"); | |
44 | |
45 // Release the main function. | |
46 g_handler_data->handler_done.Signal(); | |
47 } | 104 } |
48 | 105 |
49 // Fall through to the next handler in turn. | 106 if (!background_thread_.task_runner()->PostTask(FROM_HERE, |
50 return FALSE; | 107 base::Bind(&BrowserMonitor::Watch, base::Unretained(this)))) { |
| 108 background_thread_.Stop(); |
| 109 return false; |
| 110 } |
| 111 |
| 112 return true; |
| 113 } |
| 114 |
| 115 void BrowserMonitor::OnEndSession(LPARAM lparam) { |
| 116 DCHECK_EQ(main_thread_, base::MessageLoopProxy::current()); |
| 117 |
| 118 exit_funnel_.RecordEvent(L"WatcherLogoff"); |
| 119 |
| 120 // Belt-and-suspenders; make sure our message loop exits ASAP. |
| 121 if (browser_exited_) |
| 122 run_loop_->Quit(); |
| 123 } |
| 124 |
| 125 void BrowserMonitor::Watch() { |
| 126 // This needs to run on an IO thread. |
| 127 DCHECK_NE(main_thread_, base::MessageLoopProxy::current()); |
| 128 |
| 129 exit_code_watcher_.WaitForExit(); |
| 130 exit_funnel_.RecordEvent(L"BrowserExit"); |
| 131 |
| 132 main_thread_->PostTask(FROM_HERE, |
| 133 base::Bind(&BrowserMonitor::BrowserExited, base::Unretained(this))); |
| 134 } |
| 135 |
| 136 void BrowserMonitor::BrowserExited() { |
| 137 // This runs in the main thread. |
| 138 DCHECK_EQ(main_thread_, base::MessageLoopProxy::current()); |
| 139 |
| 140 // Note that the browser has exited. |
| 141 browser_exited_ = true; |
| 142 |
| 143 // Our background thread has served it's purpose. |
| 144 background_thread_.Stop(); |
| 145 |
| 146 const int exit_code = exit_code_watcher_.exit_code(); |
| 147 if (exit_code >= 0 && exit_code <= 28) { |
| 148 // The browser exited with a well-known exit code, quit this process |
| 149 // immediately. |
| 150 run_loop_->Quit(); |
| 151 } else { |
| 152 // The browser exited abnormally, wait around for a little bit to see |
| 153 // whether this instance will get a logoff message. |
| 154 main_thread_->PostDelayedTask(FROM_HERE, |
| 155 run_loop_->QuitClosure(), |
| 156 base::TimeDelta::FromSeconds(30)); |
| 157 } |
51 } | 158 } |
52 | 159 |
53 } // namespace | 160 } // namespace |
54 | 161 |
55 // The main entry point to the watcher, declared as extern "C" to avoid name | 162 // The main entry point to the watcher, declared as extern "C" to avoid name |
56 // mangling. | 163 // mangling. |
57 extern "C" int WatcherMain(const base::char16* registry_path, | 164 extern "C" int WatcherMain(const base::char16* registry_path, |
58 HANDLE process_handle) { | 165 HANDLE process_handle) { |
59 base::Process process(process_handle); | 166 base::Process process(process_handle); |
| 167 |
60 // The exit manager is in charge of calling the dtors of singletons. | 168 // The exit manager is in charge of calling the dtors of singletons. |
61 base::AtExitManager exit_manager; | 169 base::AtExitManager exit_manager; |
62 // Initialize the commandline singleton from the environment. | 170 // Initialize the commandline singleton from the environment. |
63 base::CommandLine::Init(0, nullptr); | 171 base::CommandLine::Init(0, nullptr); |
64 | 172 |
65 logging::LogEventProvider::Initialize(kChromeWatcherTraceProviderName); | 173 logging::LogEventProvider::Initialize(kChromeWatcherTraceProviderName); |
66 | 174 |
67 // Arrange to be shut down as late as possible, as we want to outlive | 175 // Arrange to be shut down as late as possible, as we want to outlive |
68 // chrome.exe in order to report its exit status. | 176 // chrome.exe in order to report its exit status. |
69 // TODO(siggi): Does this (windowless) process need to register a console | |
70 // handler too, in order to get notice of logoff events? | |
71 ::SetProcessShutdownParameters(0x100, SHUTDOWN_NORETRY); | 177 ::SetProcessShutdownParameters(0x100, SHUTDOWN_NORETRY); |
72 | 178 |
73 browser_watcher::ExitCodeWatcher exit_code_watcher(registry_path); | 179 // Run a UI message loop on the main thread. |
74 int ret = 1; | 180 base::MessageLoop msg_loop(base::MessageLoop::TYPE_UI); |
75 // Attempt to wait on our parent process, and record its exit status. | 181 msg_loop.set_thread_name("WatcherMainThread"); |
76 if (exit_code_watcher.Initialize(process.Duplicate())) { | |
77 // Set up a console control handler, and provide it the data it needs | |
78 // to record into the browser's exit funnel. | |
79 HandlerData data; | |
80 data.process = process.Handle(); | |
81 data.registry_path = registry_path; | |
82 g_handler_data = &data; | |
83 ::SetConsoleCtrlHandler(&ConsoleCtrlHandler, TRUE /* Add */); | |
84 | 182 |
85 // Wait on the process. | 183 base::RunLoop run_loop; |
86 exit_code_watcher.WaitForExit(); | 184 BrowserMonitor monitor(&run_loop, registry_path); |
| 185 if (!monitor.StartWatching(registry_path, process.Pass())) |
| 186 return 1; |
87 | 187 |
88 browser_watcher::ExitFunnel funnel; | 188 run_loop.Run(); |
89 funnel.Init(registry_path, process.Handle()); | |
90 funnel.RecordEvent(L"BrowserExit"); | |
91 | |
92 // Chrome/content exit codes are currently in the range of 0-28. | |
93 // Anything outside this range is strange, so watch harder. | |
94 int exit_code = exit_code_watcher.exit_code(); | |
95 if (exit_code < 0 || exit_code > 28) { | |
96 // Wait for a max of 30 seconds to see whether we get notified of logoff. | |
97 data.handler_done.TimedWait(base::TimeDelta::FromSeconds(30)); | |
98 } | |
99 | |
100 // Remove the console control handler. | |
101 // There is a potential race here, where the handler might be executing | |
102 // already as we fall through here. Hopefully SetConsoleCtrlHandler is | |
103 // synchronizing against handler execution, for there's no other way to | |
104 // close the race. Thankfully we'll just get snuffed out, as this is logoff. | |
105 ::SetConsoleCtrlHandler(&ConsoleCtrlHandler, FALSE /* Add */); | |
106 g_handler_data = nullptr; | |
107 | |
108 ret = 0; | |
109 } | |
110 | 189 |
111 // Wind logging down. | 190 // Wind logging down. |
112 logging::LogEventProvider::Uninitialize(); | 191 logging::LogEventProvider::Uninitialize(); |
113 | 192 |
114 return ret; | 193 return 0; |
115 } | 194 } |
116 | 195 |
117 static_assert(base::is_same<decltype(&WatcherMain), | 196 static_assert(base::is_same<decltype(&WatcherMain), |
118 browser_watcher::WatcherMainFunction>::value, | 197 browser_watcher::WatcherMainFunction>::value, |
119 "WatcherMain() has wrong type"); | 198 "WatcherMain() has wrong type"); |
OLD | NEW |