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
,
140 switches::kEnableLogging
,
141 switches::kIgnoreUrlFetcherCertRequests
,
143 switches::kLoggingLevel
,
145 switches::kNoServiceAutorun
,
146 switches::kUserDataDir
,
149 switches::kWaitForDebugger
,
151 cmd_line
->CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
153 arraysize(kSwitchesToCopy
));
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)));
161 void ServiceProcessControl::Disconnect() {
162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
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.
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
179 RunConnectDoneTasks();
182 // We don't need the launcher anymore.
186 bool ServiceProcessControl::OnMessageReceived(const IPC::Message
& message
) {
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()
197 void ServiceProcessControl::OnChannelConnected(int32 peer_pid
) {
198 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
200 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
201 SERVICE_EVENT_CHANNEL_CONNECTED
, SERVICE_EVENT_MAX
);
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
);
208 if (registrar_
.IsEmpty())
209 registrar_
.Add(this, chrome::NOTIFICATION_UPGRADE_RECOMMENDED
,
210 content::NotificationService::AllSources());
212 RunConnectDoneTasks();
215 void ServiceProcessControl::OnChannelError() {
216 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
218 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
219 SERVICE_EVENT_CHANNEL_ERROR
, SERVICE_EVENT_MAX
);
222 RunConnectDoneTasks();
225 bool ServiceProcessControl::Send(IPC::Message
* message
) {
226 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
229 return channel_
->Send(message
);
232 // content::NotificationObserver implementation.
233 void ServiceProcessControl::Observe(
235 const content::NotificationSource
& source
,
236 const content::NotificationDetails
& details
) {
237 if (type
== chrome::NOTIFICATION_UPGRADE_RECOMMENDED
) {
238 Send(new ServiceMsg_UpdateAvailable
);
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();
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(
260 RunHistogramsCallback();
263 void ServiceProcessControl::RunHistogramsCallback() {
264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
265 if (!histograms_callback_
.is_null()) {
266 histograms_callback_
.Run();
267 histograms_callback_
.Reset();
269 histograms_timeout_callback_
.Cancel();
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()))
281 cloud_print_info_callback_
= cloud_print_info_callback
;
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();
292 // If the service process is already running then connect to it.
293 if (!CheckServiceProcessReady())
297 UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
298 SERVICE_EVENT_HISTOGRAMS_REQUEST
,
301 if (!Send(new ServiceMsg_GetHistograms()))
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(),
312 histograms_callback_
= histograms_callback
;
316 bool ServiceProcessControl::Shutdown() {
317 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
318 bool ret
= Send(new ServiceMsg_Shutdown());
324 ServiceProcessControl
* ServiceProcessControl::GetInstance() {
325 return Singleton
<ServiceProcessControl
>::get();
328 ServiceProcessControl::Launcher::Launcher(ServiceProcessControl
* process
,
329 CommandLine
* cmd_line
)
334 process_handle_(base::kNullProcessHandle
) {
337 // Execute the command line to start the process asynchronously.
338 // After the command is executed, |task| is called with the process handle on
340 void ServiceProcessControl::Launcher::Run(const base::Closure
& task
) {
341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
343 BrowserThread::PostTask(BrowserThread::PROCESS_LAUNCHER
, FROM_HERE
,
344 base::Bind(&Launcher::DoRun
, this));
347 ServiceProcessControl::Launcher::~Launcher() {
348 CloseProcessHandle();
352 void ServiceProcessControl::Launcher::Notify() {
353 DCHECK(!notify_task_
.is_null());
355 notify_task_
.Reset();
358 void ServiceProcessControl::Launcher::CloseProcessHandle() {
359 if (process_handle_
!= base::kNullProcessHandle
) {
360 base::CloseProcessHandle(process_handle_
);
361 process_handle_
= base::kNullProcessHandle
;
365 #if !defined(OS_MACOSX)
366 void ServiceProcessControl::Launcher::DoDetectLaunched() {
367 DCHECK(!notify_task_
.is_null());
369 const uint32 kMaxLaunchDetectRetries
= 10;
370 launched_
= CheckServiceProcessReady();
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));
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),
390 void ServiceProcessControl::Launcher::DoRun() {
391 DCHECK(!notify_task_
.is_null());
393 base::LaunchOptions options
;
395 options
.start_hidden
= true;
397 if (base::LaunchProcess(*cmd_line_
, options
, &process_handle_
)) {
398 BrowserThread::PostTask(
399 BrowserThread::IO
, FROM_HERE
,
400 base::Bind(&Launcher::DoDetectLaunched
, this));
402 BrowserThread::PostTask(
403 BrowserThread::UI
, FROM_HERE
, base::Bind(&Launcher::Notify
, this));