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
~Delegate()51 WorkerProcessLauncher::Delegate::~Delegate() {
52 }
53
WorkerProcessLauncher(scoped_ptr<WorkerProcessLauncher::Delegate> launcher_delegate,WorkerProcessIpcDelegate * ipc_handler)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
~WorkerProcessLauncher()69 WorkerProcessLauncher::~WorkerProcessLauncher() {
70 DCHECK(CalledOnValidThread());
71
72 ipc_handler_ = NULL;
73 StopWorker();
74 }
75
Crash(const tracked_objects::Location & location)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
Send(IPC::Message * message)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
OnProcessLaunched(base::win::ScopedHandle worker_process)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
OnFatalError()125 void WorkerProcessLauncher::OnFatalError() {
126 DCHECK(CalledOnValidThread());
127
128 StopWorker();
129 }
130
OnMessageReceived(const IPC::Message & message)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
OnChannelConnected(int32 peer_pid)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
OnChannelError()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
OnObjectSignaled(HANDLE object)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
LaunchWorker()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
RecordLaunchResult()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
RecordSuccessfulLaunchForTest()216 void WorkerProcessLauncher::RecordSuccessfulLaunchForTest() {
217 DCHECK(CalledOnValidThread());
218
219 if (launch_result_timer_.IsRunning()) {
220 launch_result_timer_.Stop();
221 RecordLaunchResult();
222 }
223 }
224
SetKillProcessTimeoutForTest(const base::TimeDelta & timeout)225 void WorkerProcessLauncher::SetKillProcessTimeoutForTest(
226 const base::TimeDelta& timeout) {
227 DCHECK(CalledOnValidThread());
228
229 kill_process_timeout_ = timeout;
230 }
231
StopWorker()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