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.
5 #include "chrome/browser/service_process/service_process_control.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"
30 using content::BrowserThread
;
31 using content::ChildProcessHost
;
33 // ServiceProcessControl implementation.
34 ServiceProcessControl::ServiceProcessControl() {
37 ServiceProcessControl::~ServiceProcessControl() {
40 void ServiceProcessControl::ConnectInternal() {
41 // If the channel has already been established then we run the task
44 RunConnectDoneTasks();
48 // Actually going to connect.
49 VLOG(1) << "Connecting to Service Process IPC Server";
51 // TODO(hclam): Handle error connecting to channel.
52 const IPC::ChannelHandle channel_id
= GetServiceProcessChannel();
53 SetChannel(new IPC::ChannelProxy(
55 IPC::Channel::MODE_NAMED_CLIENT
,
57 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO
).get()));
60 void ServiceProcessControl::SetChannel(IPC::ChannelProxy
* channel
) {
61 channel_
.reset(channel
);
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
71 tasks
.swap(connect_success_tasks_
);
72 RunAllTasksHelper(&tasks
);
73 DCHECK(tasks
.empty());
74 connect_failure_tasks_
.clear();
76 tasks
.swap(connect_failure_tasks_
);
77 RunAllTasksHelper(&tasks
);
78 DCHECK(tasks
.empty());
79 connect_success_tasks_
.clear();
84 void ServiceProcessControl::RunAllTasksHelper(TaskList
* task_list
) {
85 TaskList::iterator index
= task_list
->begin();
86 while (index
!= task_list
->end()) {
88 index
= task_list
->erase(index
);
92 bool ServiceProcessControl::IsConnected() const {
93 return channel_
!= NULL
;
96 void ServiceProcessControl::Launch(const base::Closure
& success_task
,
97 const base::Closure
& failure_task
) {
98 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
100 base::Closure failure
= failure_task
;
101 if (!success_task
.is_null())
102 connect_success_tasks_
.push_back(success_task
);
104 if (!failure
.is_null())
105 connect_failure_tasks_
.push_back(failure
);
107 // If we already in the process of launching, then we are done.
111 // If the service process is already running then connects to it.
112 if (CheckServiceProcessReady()) {
117 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", SERVICE_EVENT_LAUNCH
,
120 // A service process should have a different mechanism for starting, but now
121 // we start it as if it is a child process.
123 #if defined(OS_LINUX)
124 int flags
= ChildProcessHost::CHILD_ALLOW_SELF
;
126 int flags
= ChildProcessHost::CHILD_NORMAL
;
129 base::FilePath exe_path
= ChildProcessHost::GetChildPath(flags
);
130 if (exe_path
.empty())
131 NOTREACHED() << "Unable to get service process binary name.";
133 CommandLine
* cmd_line
= new CommandLine(exe_path
);
134 cmd_line
->AppendSwitchASCII(switches::kProcessType
,
135 switches::kServiceProcess
);
137 static const char* const kSwitchesToCopy
[] = {
138 switches::kCloudPrintServiceURL
,
139 switches::kCloudPrintSetupProxy
,
141 switches::kEnableCloudPrintXps
,
143 switches::kEnableLogging
,
144 switches::kIgnoreUrlFetcherCertRequests
,
146 switches::kLoggingLevel
,
148 switches::kNoServiceAutorun
,
149 switches::kUserDataDir
,
152 switches::kWaitForDebugger
,
154 cmd_line
->CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
156 arraysize(kSwitchesToCopy
));
158 // And then start the process asynchronously.
159 launcher_
= new Launcher(this, cmd_line
);
160 launcher_
->Run(base::Bind(&ServiceProcessControl::OnProcessLaunched
,
161 base::Unretained(this)));
164 void ServiceProcessControl::Disconnect() {
165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
169 void ServiceProcessControl::OnProcessLaunched() {
170 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
171 if (launcher_
->launched()) {
172 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
173 SERVICE_EVENT_LAUNCHED
, SERVICE_EVENT_MAX
);
174 // After we have successfully created the service process we try to connect
175 // to it. The launch task is transfered to a connect task.
178 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
179 SERVICE_EVENT_LAUNCH_FAILED
, SERVICE_EVENT_MAX
);
180 // If we don't have process handle that means launching the service process
182 RunConnectDoneTasks();
185 // We don't need the launcher anymore.
189 bool ServiceProcessControl::OnMessageReceived(const IPC::Message
& message
) {
191 IPC_BEGIN_MESSAGE_MAP(ServiceProcessControl
, message
)
192 IPC_MESSAGE_HANDLER(ServiceHostMsg_CloudPrintProxy_Info
,
193 OnCloudPrintProxyInfo
)
194 IPC_MESSAGE_HANDLER(ServiceHostMsg_Histograms
, OnHistograms
)
195 IPC_MESSAGE_UNHANDLED(handled
= false)
196 IPC_END_MESSAGE_MAP()
200 void ServiceProcessControl::OnChannelConnected(int32 peer_pid
) {
201 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
203 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
204 SERVICE_EVENT_CHANNEL_CONNECTED
, SERVICE_EVENT_MAX
);
206 // We just established a channel with the service process. Notify it if an
207 // upgrade is available.
208 if (UpgradeDetector::GetInstance()->notify_upgrade()) {
209 Send(new ServiceMsg_UpdateAvailable
);
211 if (registrar_
.IsEmpty())
212 registrar_
.Add(this, chrome::NOTIFICATION_UPGRADE_RECOMMENDED
,
213 content::NotificationService::AllSources());
215 RunConnectDoneTasks();
218 void ServiceProcessControl::OnChannelError() {
219 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
221 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
222 SERVICE_EVENT_CHANNEL_ERROR
, SERVICE_EVENT_MAX
);
225 RunConnectDoneTasks();
228 bool ServiceProcessControl::Send(IPC::Message
* message
) {
229 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
232 return channel_
->Send(message
);
235 // content::NotificationObserver implementation.
236 void ServiceProcessControl::Observe(
238 const content::NotificationSource
& source
,
239 const content::NotificationDetails
& details
) {
240 if (type
== chrome::NOTIFICATION_UPGRADE_RECOMMENDED
) {
241 Send(new ServiceMsg_UpdateAvailable
);
245 void ServiceProcessControl::OnCloudPrintProxyInfo(
246 const cloud_print::CloudPrintProxyInfo
& proxy_info
) {
247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
248 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
249 SERVICE_EVENT_INFO_REPLY
, SERVICE_EVENT_MAX
);
250 if (!cloud_print_info_callback_
.is_null()) {
251 cloud_print_info_callback_
.Run(proxy_info
);
252 cloud_print_info_callback_
.Reset();
256 void ServiceProcessControl::OnHistograms(
257 const std::vector
<std::string
>& pickled_histograms
) {
258 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
259 SERVICE_EVENT_HISTOGRAMS_REPLY
, SERVICE_EVENT_MAX
);
260 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
261 base::HistogramDeltaSerialization::DeserializeAndAddSamples(
263 RunHistogramsCallback();
266 void ServiceProcessControl::RunHistogramsCallback() {
267 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
268 if (!histograms_callback_
.is_null()) {
269 histograms_callback_
.Run();
270 histograms_callback_
.Reset();
272 histograms_timeout_callback_
.Cancel();
275 bool ServiceProcessControl::GetCloudPrintProxyInfo(
276 const CloudPrintProxyInfoHandler
& cloud_print_info_callback
) {
277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
278 DCHECK(!cloud_print_info_callback
.is_null());
279 cloud_print_info_callback_
.Reset();
280 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
281 SERVICE_EVENT_INFO_REQUEST
, SERVICE_EVENT_MAX
);
282 if (!Send(new ServiceMsg_GetCloudPrintProxyInfo()))
284 cloud_print_info_callback_
= cloud_print_info_callback
;
288 bool ServiceProcessControl::GetHistograms(
289 const base::Closure
& histograms_callback
,
290 const base::TimeDelta
& timeout
) {
291 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
292 DCHECK(!histograms_callback
.is_null());
293 histograms_callback_
.Reset();
295 // If the service process is already running then connect to it.
296 if (!CheckServiceProcessReady())
300 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
301 SERVICE_EVENT_HISTOGRAMS_REQUEST
,
304 if (!Send(new ServiceMsg_GetHistograms()))
307 // Run timeout task to make sure |histograms_callback| is called.
308 histograms_timeout_callback_
.Reset(
309 base::Bind(&ServiceProcessControl::RunHistogramsCallback
,
310 base::Unretained(this)));
311 BrowserThread::PostDelayedTask(BrowserThread::UI
, FROM_HERE
,
312 histograms_timeout_callback_
.callback(),
315 histograms_callback_
= histograms_callback
;
319 bool ServiceProcessControl::Shutdown() {
320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
321 bool ret
= Send(new ServiceMsg_Shutdown());
327 ServiceProcessControl
* ServiceProcessControl::GetInstance() {
328 return Singleton
<ServiceProcessControl
>::get();
331 ServiceProcessControl::Launcher::Launcher(ServiceProcessControl
* process
,
332 CommandLine
* cmd_line
)
337 process_handle_(base::kNullProcessHandle
) {
340 // Execute the command line to start the process asynchronously.
341 // After the command is executed, |task| is called with the process handle on
343 void ServiceProcessControl::Launcher::Run(const base::Closure
& task
) {
344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
346 BrowserThread::PostTask(BrowserThread::PROCESS_LAUNCHER
, FROM_HERE
,
347 base::Bind(&Launcher::DoRun
, this));
350 ServiceProcessControl::Launcher::~Launcher() {
351 CloseProcessHandle();
355 void ServiceProcessControl::Launcher::Notify() {
356 DCHECK(!notify_task_
.is_null());
358 notify_task_
.Reset();
361 void ServiceProcessControl::Launcher::CloseProcessHandle() {
362 if (process_handle_
!= base::kNullProcessHandle
) {
363 base::CloseProcessHandle(process_handle_
);
364 process_handle_
= base::kNullProcessHandle
;
368 #if !defined(OS_MACOSX)
369 void ServiceProcessControl::Launcher::DoDetectLaunched() {
370 DCHECK(!notify_task_
.is_null());
372 const uint32 kMaxLaunchDetectRetries
= 10;
373 launched_
= CheckServiceProcessReady();
376 if (launched_
|| (retry_count_
>= kMaxLaunchDetectRetries
) ||
377 base::WaitForExitCodeWithTimeout(process_handle_
, &exit_code
,
378 base::TimeDelta())) {
379 CloseProcessHandle();
380 BrowserThread::PostTask(
381 BrowserThread::UI
, FROM_HERE
, base::Bind(&Launcher::Notify
, this));
386 // If the service process is not launched yet then check again in 2 seconds.
387 const base::TimeDelta kDetectLaunchRetry
= base::TimeDelta::FromSeconds(2);
388 base::MessageLoop::current()->PostDelayedTask(
389 FROM_HERE
, base::Bind(&Launcher::DoDetectLaunched
, this),
393 void ServiceProcessControl::Launcher::DoRun() {
394 DCHECK(!notify_task_
.is_null());
396 base::LaunchOptions options
;
398 options
.start_hidden
= true;
400 if (base::LaunchProcess(*cmd_line_
, options
, &process_handle_
)) {
401 BrowserThread::PostTask(
402 BrowserThread::IO
, FROM_HERE
,
403 base::Bind(&Launcher::DoDetectLaunched
, this));
405 BrowserThread::PostTask(
406 BrowserThread::UI
, FROM_HERE
, base::Bind(&Launcher::Notify
, this));