| 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 |