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_process/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 407