Home | History | Annotate | Download | only in win
      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 "remoting/host/win/worker_process_launcher.h"
      6 
      7 #include "base/location.h"
      8 #include "base/logging.h"
      9 #include "base/single_thread_task_runner.h"
     10 #include "base/time/time.h"
     11 #include "base/win/windows_version.h"
     12 #include "ipc/ipc_message.h"
     13 #include "remoting/host/chromoting_messages.h"
     14 #include "remoting/host/host_exit_codes.h"
     15 #include "remoting/host/worker_process_ipc_delegate.h"
     16 
     17 using base::TimeDelta;
     18 using base::win::ScopedHandle;
     19 
     20 const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
     21   // Number of initial errors (in sequence) to ignore before applying
     22   // exponential back-off rules.
     23   0,
     24 
     25   // Initial delay for exponential back-off in ms.
     26   100,
     27 
     28   // Factor by which the waiting time will be multiplied.
     29   2,
     30 
     31   // Fuzzing percentage. ex: 10% will spread requests randomly
     32   // between 90%-100% of the calculated time.
     33   0,
     34 
     35   // Maximum amount of time we are willing to delay our request in ms.
     36   60000,
     37 
     38   // Time to keep an entry from being discarded even when it
     39   // has no significant state, -1 to never discard.
     40   -1,
     41 
     42   // Don't use initial delay unless the last request was an error.
     43   false,
     44 };
     45 
     46 const int kKillProcessTimeoutSeconds = 5;
     47 const int kLaunchResultTimeoutSeconds = 5;
     48 
     49 namespace remoting {
     50 
     51 WorkerProcessLauncher::Delegate::~Delegate() {
     52 }
     53 
     54 WorkerProcessLauncher::WorkerProcessLauncher(
     55     scoped_ptr<WorkerProcessLauncher::Delegate> launcher_delegate,
     56     WorkerProcessIpcDelegate* ipc_handler)
     57     : ipc_handler_(ipc_handler),
     58       launcher_delegate_(launcher_delegate.Pass()),
     59       exit_code_(CONTROL_C_EXIT),
     60       ipc_enabled_(false),
     61       kill_process_timeout_(
     62           base::TimeDelta::FromSeconds(kKillProcessTimeoutSeconds)),
     63       launch_backoff_(&kDefaultBackoffPolicy) {
     64   DCHECK(ipc_handler_ != NULL);
     65 
     66   LaunchWorker();
     67 }
     68 
     69 WorkerProcessLauncher::~WorkerProcessLauncher() {
     70   DCHECK(CalledOnValidThread());
     71 
     72   ipc_handler_ = NULL;
     73   StopWorker();
     74 }
     75 
     76 void WorkerProcessLauncher::Crash(
     77     const tracked_objects::Location& location) {
     78   DCHECK(CalledOnValidThread());
     79 
     80   // Ask the worker process to crash voluntarily if it is still connected.
     81   if (ipc_enabled_) {
     82     Send(new ChromotingDaemonMsg_Crash(location.function_name(),
     83                                        location.file_name(),
     84                                        location.line_number()));
     85   }
     86 
     87   // Close the channel and ignore any not yet processed messages.
     88   launcher_delegate_->CloseChannel();
     89   ipc_enabled_ = false;
     90 
     91   // Give the worker process some time to crash.
     92   if (!kill_process_timer_.IsRunning()) {
     93     kill_process_timer_.Start(FROM_HERE, kill_process_timeout_, this,
     94                               &WorkerProcessLauncher::StopWorker);
     95   }
     96 }
     97 
     98 void WorkerProcessLauncher::Send(IPC::Message* message) {
     99   DCHECK(CalledOnValidThread());
    100 
    101   if (ipc_enabled_) {
    102     launcher_delegate_->Send(message);
    103   } else {
    104     delete message;
    105   }
    106 }
    107 
    108 void WorkerProcessLauncher::OnProcessLaunched(
    109     base::win::ScopedHandle worker_process) {
    110   DCHECK(CalledOnValidThread());
    111   DCHECK(!ipc_enabled_);
    112   DCHECK(!launch_timer_.IsRunning());
    113   DCHECK(!process_watcher_.GetWatchedObject());
    114   DCHECK(!worker_process_.IsValid());
    115 
    116   if (!process_watcher_.StartWatching(worker_process, this)) {
    117     StopWorker();
    118     return;
    119   }
    120 
    121   ipc_enabled_ = true;
    122   worker_process_ = worker_process.Pass();
    123 }
    124 
    125 void WorkerProcessLauncher::OnFatalError() {
    126   DCHECK(CalledOnValidThread());
    127 
    128   StopWorker();
    129 }
    130 
    131 bool WorkerProcessLauncher::OnMessageReceived(
    132   const IPC::Message& message) {
    133   DCHECK(CalledOnValidThread());
    134 
    135   if (!ipc_enabled_)
    136     return false;
    137 
    138   return ipc_handler_->OnMessageReceived(message);
    139 }
    140 
    141 void WorkerProcessLauncher::OnChannelConnected(int32 peer_pid) {
    142   DCHECK(CalledOnValidThread());
    143 
    144   if (!ipc_enabled_)
    145     return;
    146 
    147   // This can result in |this| being deleted, so this call must be the last in
    148   // this method.
    149   ipc_handler_->OnChannelConnected(peer_pid);
    150 }
    151 
    152 void WorkerProcessLauncher::OnChannelError() {
    153   DCHECK(CalledOnValidThread());
    154 
    155   // Schedule a delayed termination of the worker process. Usually, the pipe is
    156   // disconnected when the worker process is about to exit. Waiting a little bit
    157   // here allows the worker to exit completely and so, notify
    158   // |process_watcher_|. As the result KillProcess() will not be called and
    159   // the original exit code reported by the worker process will be retrieved.
    160   if (!kill_process_timer_.IsRunning()) {
    161     kill_process_timer_.Start(FROM_HERE, kill_process_timeout_, this,
    162                               &WorkerProcessLauncher::StopWorker);
    163   }
    164 }
    165 
    166 void WorkerProcessLauncher::OnObjectSignaled(HANDLE object) {
    167   DCHECK(CalledOnValidThread());
    168   DCHECK(!process_watcher_.GetWatchedObject());
    169   DCHECK_EQ(exit_code_, CONTROL_C_EXIT);
    170   DCHECK_EQ(worker_process_, object);
    171 
    172   // Get exit code of the worker process if it is available.
    173   if (!::GetExitCodeProcess(worker_process_, &exit_code_)) {
    174     LOG_GETLASTERROR(INFO)
    175         << "Failed to query the exit code of the worker process";
    176     exit_code_ = CONTROL_C_EXIT;
    177   }
    178 
    179   worker_process_.Close();
    180   StopWorker();
    181 }
    182 
    183 void WorkerProcessLauncher::LaunchWorker() {
    184   DCHECK(CalledOnValidThread());
    185   DCHECK(!ipc_enabled_);
    186   DCHECK(!kill_process_timer_.IsRunning());
    187   DCHECK(!launch_timer_.IsRunning());
    188   DCHECK(!process_watcher_.GetWatchedObject());
    189   DCHECK(!launch_result_timer_.IsRunning());
    190 
    191   exit_code_ = CONTROL_C_EXIT;
    192 
    193   // Make sure launching a process will not take forever.
    194   launch_result_timer_.Start(
    195       FROM_HERE, base::TimeDelta::FromSeconds(kLaunchResultTimeoutSeconds),
    196       this, &WorkerProcessLauncher::RecordLaunchResult);
    197 
    198   launcher_delegate_->LaunchProcess(this);
    199 }
    200 
    201 void WorkerProcessLauncher::RecordLaunchResult() {
    202   DCHECK(CalledOnValidThread());
    203 
    204   if (!worker_process_.IsValid()) {
    205     LOG(WARNING) << "A worker process failed to start within "
    206                  << kLaunchResultTimeoutSeconds << " seconds.";
    207 
    208     launch_backoff_.InformOfRequest(false);
    209     StopWorker();
    210     return;
    211   }
    212 
    213   // Assume success if the worker process has been running for a few seconds.
    214   launch_backoff_.InformOfRequest(true);
    215 }
    216 
    217 void WorkerProcessLauncher::RecordSuccessfulLaunchForTest() {
    218   DCHECK(CalledOnValidThread());
    219 
    220   if (launch_result_timer_.IsRunning()) {
    221     launch_result_timer_.Stop();
    222     RecordLaunchResult();
    223   }
    224 }
    225 
    226 void WorkerProcessLauncher::SetKillProcessTimeoutForTest(
    227     const base::TimeDelta& timeout) {
    228   DCHECK(CalledOnValidThread());
    229 
    230   kill_process_timeout_ = timeout;
    231 }
    232 
    233 void WorkerProcessLauncher::StopWorker() {
    234   DCHECK(CalledOnValidThread());
    235 
    236   // Record a launch failure if the process exited too soon.
    237   if (launch_result_timer_.IsRunning()) {
    238     launch_backoff_.InformOfRequest(false);
    239     launch_result_timer_.Stop();
    240   }
    241 
    242   // Ignore any remaining IPC messages.
    243   ipc_enabled_ = false;
    244 
    245   // Stop monitoring the worker process.
    246   process_watcher_.StopWatching();
    247   worker_process_.Close();
    248 
    249   kill_process_timer_.Stop();
    250   launcher_delegate_->KillProcess();
    251 
    252   // Do not relaunch the worker process if the caller has asked us to stop.
    253   if (stopping())
    254     return;
    255 
    256   // Stop trying to restart the worker process if it exited due to
    257   // misconfiguration.
    258   if (kMinPermanentErrorExitCode <= exit_code_ &&
    259       exit_code_ <= kMaxPermanentErrorExitCode) {
    260     ipc_handler_->OnPermanentError(exit_code_);
    261     return;
    262   }
    263 
    264   // Schedule the next attempt to launch the worker process.
    265   launch_timer_.Start(FROM_HERE, launch_backoff_.GetTimeUntilRelease(), this,
    266                       &WorkerProcessLauncher::LaunchWorker);
    267 }
    268 
    269 } // namespace remoting
    270