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