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