OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/service/service_process_control.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/bind_helpers.h" | |
9 #include "base/command_line.h" | |
10 #include "base/files/file_path.h" | |
11 #include "base/metrics/histogram_base.h" | |
12 #include "base/metrics/histogram_delta_serialization.h" | |
13 #include "base/process/kill.h" | |
14 #include "base/process/launch.h" | |
15 #include "base/stl_util.h" | |
16 #include "base/threading/thread.h" | |
17 #include "base/threading/thread_restrictions.h" | |
18 #include "chrome/browser/browser_process.h" | |
19 #include "chrome/browser/chrome_notification_types.h" | |
20 #include "chrome/browser/upgrade_detector.h" | |
21 #include "chrome/common/chrome_switches.h" | |
22 #include "chrome/common/service_messages.h" | |
23 #include "chrome/common/service_process_util.h" | |
24 #include "content/public/browser/browser_thread.h" | |
25 #include "content/public/browser/notification_service.h" | |
26 #include "content/public/common/child_process_host.h" | |
27 #include "google_apis/gaia/gaia_switches.h" | |
28 #include "ui/base/ui_base_switches.h" | |
29 | |
30 using content::BrowserThread; | |
31 using content::ChildProcessHost; | |
32 | |
33 // ServiceProcessControl implementation. | |
34 ServiceProcessControl::ServiceProcessControl() { | |
35 } | |
36 | |
37 ServiceProcessControl::~ServiceProcessControl() { | |
38 } | |
39 | |
40 void ServiceProcessControl::ConnectInternal() { | |
41 // If the channel has already been established then we run the task | |
42 // and return. | |
43 if (channel_.get()) { | |
44 RunConnectDoneTasks(); | |
45 return; | |
46 } | |
47 | |
48 // Actually going to connect. | |
49 VLOG(1) << "Connecting to Service Process IPC Server"; | |
50 | |
51 // TODO(hclam): Handle error connecting to channel. | |
52 const IPC::ChannelHandle channel_id = GetServiceProcessChannel(); | |
53 SetChannel(new IPC::ChannelProxy( | |
54 channel_id, | |
55 IPC::Channel::MODE_NAMED_CLIENT, | |
56 this, | |
57 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get())); | |
58 } | |
59 | |
60 void ServiceProcessControl::SetChannel(IPC::ChannelProxy* channel) { | |
61 channel_.reset(channel); | |
62 } | |
63 | |
64 void ServiceProcessControl::RunConnectDoneTasks() { | |
65 // The tasks executed here may add more tasks to the vector. So copy | |
66 // them to the stack before executing them. This way recursion is | |
67 // avoided. | |
68 TaskList tasks; | |
69 | |
70 if (IsConnected()) { | |
71 tasks.swap(connect_success_tasks_); | |
72 RunAllTasksHelper(&tasks); | |
73 DCHECK(tasks.empty()); | |
74 connect_failure_tasks_.clear(); | |
75 } else { | |
76 tasks.swap(connect_failure_tasks_); | |
77 RunAllTasksHelper(&tasks); | |
78 DCHECK(tasks.empty()); | |
79 connect_success_tasks_.clear(); | |
80 } | |
81 } | |
82 | |
83 // static | |
84 void ServiceProcessControl::RunAllTasksHelper(TaskList* task_list) { | |
85 TaskList::iterator index = task_list->begin(); | |
86 while (index != task_list->end()) { | |
87 (*index).Run(); | |
88 index = task_list->erase(index); | |
89 } | |
90 } | |
91 | |
92 bool ServiceProcessControl::IsConnected() const { | |
93 return channel_ != NULL; | |
94 } | |
95 | |
96 void ServiceProcessControl::Launch(const base::Closure& success_task, | |
97 const base::Closure& failure_task) { | |
98 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
99 | |
100 base::Closure failure = failure_task; | |
101 if (!success_task.is_null()) | |
102 connect_success_tasks_.push_back(success_task); | |
103 | |
104 if (!failure.is_null()) | |
105 connect_failure_tasks_.push_back(failure); | |
106 | |
107 // If we already in the process of launching, then we are done. | |
108 if (launcher_.get()) | |
109 return; | |
110 | |
111 // If the service process is already running then connects to it. | |
112 if (CheckServiceProcessReady()) { | |
113 ConnectInternal(); | |
114 return; | |
115 } | |
116 | |
117 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", SERVICE_EVENT_LAUNCH, | |
118 SERVICE_EVENT_MAX); | |
119 | |
120 // A service process should have a different mechanism for starting, but now | |
121 // we start it as if it is a child process. | |
122 | |
123 #if defined(OS_LINUX) | |
124 int flags = ChildProcessHost::CHILD_ALLOW_SELF; | |
125 #else | |
126 int flags = ChildProcessHost::CHILD_NORMAL; | |
127 #endif | |
128 | |
129 base::FilePath exe_path = ChildProcessHost::GetChildPath(flags); | |
130 if (exe_path.empty()) | |
131 NOTREACHED() << "Unable to get service process binary name."; | |
132 | |
133 CommandLine* cmd_line = new CommandLine(exe_path); | |
134 cmd_line->AppendSwitchASCII(switches::kProcessType, | |
135 switches::kServiceProcess); | |
136 | |
137 static const char* const kSwitchesToCopy[] = { | |
138 switches::kCloudPrintServiceURL, | |
139 switches::kCloudPrintSetupProxy, | |
140 switches::kEnableLogging, | |
141 switches::kIgnoreUrlFetcherCertRequests, | |
142 switches::kLang, | |
143 switches::kLoggingLevel, | |
144 switches::kLsoUrl, | |
145 switches::kNoServiceAutorun, | |
146 switches::kUserDataDir, | |
147 switches::kV, | |
148 switches::kVModule, | |
149 switches::kWaitForDebugger, | |
150 }; | |
151 cmd_line->CopySwitchesFrom(*CommandLine::ForCurrentProcess(), | |
152 kSwitchesToCopy, | |
153 arraysize(kSwitchesToCopy)); | |
154 | |
155 // And then start the process asynchronously. | |
156 launcher_ = new Launcher(this, cmd_line); | |
157 launcher_->Run(base::Bind(&ServiceProcessControl::OnProcessLaunched, | |
158 base::Unretained(this))); | |
159 } | |
160 | |
161 void ServiceProcessControl::Disconnect() { | |
162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
163 channel_.reset(); | |
164 } | |
165 | |
166 void ServiceProcessControl::OnProcessLaunched() { | |
167 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
168 if (launcher_->launched()) { | |
169 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | |
170 SERVICE_EVENT_LAUNCHED, SERVICE_EVENT_MAX); | |
171 // After we have successfully created the service process we try to connect | |
172 // to it. The launch task is transfered to a connect task. | |
173 ConnectInternal(); | |
174 } else { | |
175 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | |
176 SERVICE_EVENT_LAUNCH_FAILED, SERVICE_EVENT_MAX); | |
177 // If we don't have process handle that means launching the service process | |
178 // has failed. | |
179 RunConnectDoneTasks(); | |
180 } | |
181 | |
182 // We don't need the launcher anymore. | |
183 launcher_ = NULL; | |
184 } | |
185 | |
186 bool ServiceProcessControl::OnMessageReceived(const IPC::Message& message) { | |
187 bool handled = true; | |
188 IPC_BEGIN_MESSAGE_MAP(ServiceProcessControl, message) | |
189 IPC_MESSAGE_HANDLER(ServiceHostMsg_CloudPrintProxy_Info, | |
190 OnCloudPrintProxyInfo) | |
191 IPC_MESSAGE_HANDLER(ServiceHostMsg_Histograms, OnHistograms) | |
192 IPC_MESSAGE_UNHANDLED(handled = false) | |
193 IPC_END_MESSAGE_MAP() | |
194 return handled; | |
195 } | |
196 | |
197 void ServiceProcessControl::OnChannelConnected(int32 peer_pid) { | |
198 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
199 | |
200 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | |
201 SERVICE_EVENT_CHANNEL_CONNECTED, SERVICE_EVENT_MAX); | |
202 | |
203 // We just established a channel with the service process. Notify it if an | |
204 // upgrade is available. | |
205 if (UpgradeDetector::GetInstance()->notify_upgrade()) { | |
206 Send(new ServiceMsg_UpdateAvailable); | |
207 } else { | |
208 if (registrar_.IsEmpty()) | |
209 registrar_.Add(this, chrome::NOTIFICATION_UPGRADE_RECOMMENDED, | |
210 content::NotificationService::AllSources()); | |
211 } | |
212 RunConnectDoneTasks(); | |
213 } | |
214 | |
215 void ServiceProcessControl::OnChannelError() { | |
216 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
217 | |
218 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | |
219 SERVICE_EVENT_CHANNEL_ERROR, SERVICE_EVENT_MAX); | |
220 | |
221 channel_.reset(); | |
222 RunConnectDoneTasks(); | |
223 } | |
224 | |
225 bool ServiceProcessControl::Send(IPC::Message* message) { | |
226 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
227 if (!channel_.get()) | |
228 return false; | |
229 return channel_->Send(message); | |
230 } | |
231 | |
232 // content::NotificationObserver implementation. | |
233 void ServiceProcessControl::Observe( | |
234 int type, | |
235 const content::NotificationSource& source, | |
236 const content::NotificationDetails& details) { | |
237 if (type == chrome::NOTIFICATION_UPGRADE_RECOMMENDED) { | |
238 Send(new ServiceMsg_UpdateAvailable); | |
239 } | |
240 } | |
241 | |
242 void ServiceProcessControl::OnCloudPrintProxyInfo( | |
243 const cloud_print::CloudPrintProxyInfo& proxy_info) { | |
244 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
245 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | |
246 SERVICE_EVENT_INFO_REPLY, SERVICE_EVENT_MAX); | |
247 if (!cloud_print_info_callback_.is_null()) { | |
248 cloud_print_info_callback_.Run(proxy_info); | |
249 cloud_print_info_callback_.Reset(); | |
250 } | |
251 } | |
252 | |
253 void ServiceProcessControl::OnHistograms( | |
254 const std::vector<std::string>& pickled_histograms) { | |
255 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | |
256 SERVICE_EVENT_HISTOGRAMS_REPLY, SERVICE_EVENT_MAX); | |
257 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
258 base::HistogramDeltaSerialization::DeserializeAndAddSamples( | |
259 pickled_histograms); | |
260 RunHistogramsCallback(); | |
261 } | |
262 | |
263 void ServiceProcessControl::RunHistogramsCallback() { | |
264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
265 if (!histograms_callback_.is_null()) { | |
266 histograms_callback_.Run(); | |
267 histograms_callback_.Reset(); | |
268 } | |
269 histograms_timeout_callback_.Cancel(); | |
270 } | |
271 | |
272 bool ServiceProcessControl::GetCloudPrintProxyInfo( | |
273 const CloudPrintProxyInfoHandler& cloud_print_info_callback) { | |
274 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
275 DCHECK(!cloud_print_info_callback.is_null()); | |
276 cloud_print_info_callback_.Reset(); | |
277 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | |
278 SERVICE_EVENT_INFO_REQUEST, SERVICE_EVENT_MAX); | |
279 if (!Send(new ServiceMsg_GetCloudPrintProxyInfo())) | |
280 return false; | |
281 cloud_print_info_callback_ = cloud_print_info_callback; | |
282 return true; | |
283 } | |
284 | |
285 bool ServiceProcessControl::GetHistograms( | |
286 const base::Closure& histograms_callback, | |
287 const base::TimeDelta& timeout) { | |
288 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
289 DCHECK(!histograms_callback.is_null()); | |
290 histograms_callback_.Reset(); | |
291 | |
292 // If the service process is already running then connect to it. | |
293 if (!CheckServiceProcessReady()) | |
294 return false; | |
295 ConnectInternal(); | |
296 | |
297 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | |
298 SERVICE_EVENT_HISTOGRAMS_REQUEST, | |
299 SERVICE_EVENT_MAX); | |
300 | |
301 if (!Send(new ServiceMsg_GetHistograms())) | |
302 return false; | |
303 | |
304 // Run timeout task to make sure |histograms_callback| is called. | |
305 histograms_timeout_callback_.Reset( | |
306 base::Bind(&ServiceProcessControl::RunHistogramsCallback, | |
307 base::Unretained(this))); | |
308 BrowserThread::PostDelayedTask(BrowserThread::UI, FROM_HERE, | |
309 histograms_timeout_callback_.callback(), | |
310 timeout); | |
311 | |
312 histograms_callback_ = histograms_callback; | |
313 return true; | |
314 } | |
315 | |
316 bool ServiceProcessControl::Shutdown() { | |
317 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
318 bool ret = Send(new ServiceMsg_Shutdown()); | |
319 channel_.reset(); | |
320 return ret; | |
321 } | |
322 | |
323 // static | |
324 ServiceProcessControl* ServiceProcessControl::GetInstance() { | |
325 return Singleton<ServiceProcessControl>::get(); | |
326 } | |
327 | |
328 ServiceProcessControl::Launcher::Launcher(ServiceProcessControl* process, | |
329 CommandLine* cmd_line) | |
330 : process_(process), | |
331 cmd_line_(cmd_line), | |
332 launched_(false), | |
333 retry_count_(0), | |
334 process_handle_(base::kNullProcessHandle) { | |
335 } | |
336 | |
337 // Execute the command line to start the process asynchronously. | |
338 // After the command is executed, |task| is called with the process handle on | |
339 // the UI thread. | |
340 void ServiceProcessControl::Launcher::Run(const base::Closure& task) { | |
341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
342 notify_task_ = task; | |
343 BrowserThread::PostTask(BrowserThread::PROCESS_LAUNCHER, FROM_HERE, | |
344 base::Bind(&Launcher::DoRun, this)); | |
345 } | |
346 | |
347 ServiceProcessControl::Launcher::~Launcher() { | |
348 CloseProcessHandle(); | |
349 } | |
350 | |
351 | |
352 void ServiceProcessControl::Launcher::Notify() { | |
353 DCHECK(!notify_task_.is_null()); | |
354 notify_task_.Run(); | |
355 notify_task_.Reset(); | |
356 } | |
357 | |
358 void ServiceProcessControl::Launcher::CloseProcessHandle() { | |
359 if (process_handle_ != base::kNullProcessHandle) { | |
360 base::CloseProcessHandle(process_handle_); | |
361 process_handle_ = base::kNullProcessHandle; | |
362 } | |
363 } | |
364 | |
365 #if !defined(OS_MACOSX) | |
366 void ServiceProcessControl::Launcher::DoDetectLaunched() { | |
367 DCHECK(!notify_task_.is_null()); | |
368 | |
369 const uint32 kMaxLaunchDetectRetries = 10; | |
370 launched_ = CheckServiceProcessReady(); | |
371 | |
372 int exit_code = 0; | |
373 if (launched_ || (retry_count_ >= kMaxLaunchDetectRetries) || | |
374 base::WaitForExitCodeWithTimeout(process_handle_, &exit_code, | |
375 base::TimeDelta())) { | |
376 CloseProcessHandle(); | |
377 BrowserThread::PostTask( | |
378 BrowserThread::UI, FROM_HERE, base::Bind(&Launcher::Notify, this)); | |
379 return; | |
380 } | |
381 retry_count_++; | |
382 | |
383 // If the service process is not launched yet then check again in 2 seconds. | |
384 const base::TimeDelta kDetectLaunchRetry = base::TimeDelta::FromSeconds(2); | |
385 base::MessageLoop::current()->PostDelayedTask( | |
386 FROM_HERE, base::Bind(&Launcher::DoDetectLaunched, this), | |
387 kDetectLaunchRetry); | |
388 } | |
389 | |
390 void ServiceProcessControl::Launcher::DoRun() { | |
391 DCHECK(!notify_task_.is_null()); | |
392 | |
393 base::LaunchOptions options; | |
394 #if defined(OS_WIN) | |
395 options.start_hidden = true; | |
396 #endif | |
397 if (base::LaunchProcess(*cmd_line_, options, &process_handle_)) { | |
398 BrowserThread::PostTask( | |
399 BrowserThread::IO, FROM_HERE, | |
400 base::Bind(&Launcher::DoDetectLaunched, this)); | |
401 } else { | |
402 BrowserThread::PostTask( | |
403 BrowserThread::UI, FROM_HERE, base::Bind(&Launcher::Notify, this)); | |
404 } | |
405 } | |
406 #endif // !OS_MACOSX | |
OLD | NEW |