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.Get(), 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_.Get(), object);
    171 
    172   // Get exit code of the worker process if it is available.
    173   if (!::GetExitCodeProcess(worker_process_.Get(), &exit_code_)) {
    174     PLOG(INFO) << "Failed to query the exit code of the worker process";
    175     exit_code_ = CONTROL_C_EXIT;
    176   }
    177 
    178   worker_process_.Close();
    179   StopWorker();
    180 }
    181 
    182 void WorkerProcessLauncher::LaunchWorker() {
    183   DCHECK(CalledOnValidThread());
    184   DCHECK(!ipc_enabled_);
    185   DCHECK(!kill_process_timer_.IsRunning());
    186   DCHECK(!launch_timer_.IsRunning());
    187   DCHECK(!process_watcher_.GetWatchedObject());
    188   DCHECK(!launch_result_timer_.IsRunning());
    189 
    190   exit_code_ = CONTROL_C_EXIT;
    191 
    192   // Make sure launching a process will not take forever.
    193   launch_result_timer_.Start(
    194       FROM_HERE, base::TimeDelta::FromSeconds(kLaunchResultTimeoutSeconds),
    195       this, &WorkerProcessLauncher::RecordLaunchResult);
    196 
    197   launcher_delegate_->LaunchProcess(this);
    198 }
    199 
    200 void WorkerProcessLauncher::RecordLaunchResult() {
    201   DCHECK(CalledOnValidThread());
    202 
    203   if (!worker_process_.IsValid()) {
    204     LOG(WARNING) << "A worker process failed to start within "
    205                  << kLaunchResultTimeoutSeconds << " seconds.";
    206 
    207     launch_backoff_.InformOfRequest(false);
    208     StopWorker();
    209     return;
    210   }
    211 
    212   // Assume success if the worker process has been running for a few seconds.
    213   launch_backoff_.InformOfRequest(true);
    214 }
    215 
    216 void WorkerProcessLauncher::RecordSuccessfulLaunchForTest() {
    217   DCHECK(CalledOnValidThread());
    218 
    219   if (launch_result_timer_.IsRunning()) {
    220     launch_result_timer_.Stop();
    221     RecordLaunchResult();
    222   }
    223 }
    224 
    225 void WorkerProcessLauncher::SetKillProcessTimeoutForTest(
    226     const base::TimeDelta& timeout) {
    227   DCHECK(CalledOnValidThread());
    228 
    229   kill_process_timeout_ = timeout;
    230 }
    231 
    232 void WorkerProcessLauncher::StopWorker() {
    233   DCHECK(CalledOnValidThread());
    234 
    235   // Record a launch failure if the process exited too soon.
    236   if (launch_result_timer_.IsRunning()) {
    237     launch_backoff_.InformOfRequest(false);
    238     launch_result_timer_.Stop();
    239   }
    240 
    241   // Ignore any remaining IPC messages.
    242   ipc_enabled_ = false;
    243 
    244   // Stop monitoring the worker process.
    245   process_watcher_.StopWatching();
    246   worker_process_.Close();
    247 
    248   kill_process_timer_.Stop();
    249   launcher_delegate_->KillProcess();
    250 
    251   // Do not relaunch the worker process if the caller has asked us to stop.
    252   if (stopping())
    253     return;
    254 
    255   // Stop trying to restart the worker process if it exited due to
    256   // misconfiguration.
    257   if (kMinPermanentErrorExitCode <= exit_code_ &&
    258       exit_code_ <= kMaxPermanentErrorExitCode) {
    259     ipc_handler_->OnPermanentError(exit_code_);
    260     return;
    261   }
    262 
    263   // Schedule the next attempt to launch the worker process.
    264   launch_timer_.Start(FROM_HERE, launch_backoff_.GetTimeUntilRelease(), this,
    265                       &WorkerProcessLauncher::LaunchWorker);
    266 }
    267 
    268 } // namespace remoting
    269