worker_process_launcher.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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
17using base::TimeDelta;
18using base::win::ScopedHandle;
19
20const 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
46const int kKillProcessTimeoutSeconds = 5;
47const int kLaunchResultTimeoutSeconds = 5;
48
49namespace remoting {
50
51WorkerProcessLauncher::Delegate::~Delegate() {
52}
53
54WorkerProcessLauncher::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
69WorkerProcessLauncher::~WorkerProcessLauncher() {
70  DCHECK(CalledOnValidThread());
71
72  ipc_handler_ = NULL;
73  StopWorker();
74}
75
76void 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
98void 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
108void 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
125void WorkerProcessLauncher::OnFatalError() {
126  DCHECK(CalledOnValidThread());
127
128  StopWorker();
129}
130
131bool 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
141void 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
152void 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
166void 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    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
182void 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
200void 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
216void WorkerProcessLauncher::RecordSuccessfulLaunchForTest() {
217  DCHECK(CalledOnValidThread());
218
219  if (launch_result_timer_.IsRunning()) {
220    launch_result_timer_.Stop();
221    RecordLaunchResult();
222  }
223}
224
225void WorkerProcessLauncher::SetKillProcessTimeoutForTest(
226    const base::TimeDelta& timeout) {
227  DCHECK(CalledOnValidThread());
228
229  kill_process_timeout_ = timeout;
230}
231
232void 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