OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013 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/policy/cloud/external_policy_data_updater.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/bind_helpers.h" | |
9 #include "base/callback.h" | |
10 #include "base/location.h" | |
11 #include "base/logging.h" | |
12 #include "base/sequenced_task_runner.h" | |
13 #include "base/sha1.h" | |
14 #include "base/stl_util.h" | |
15 #include "chrome/browser/policy/cloud/external_policy_data_fetcher.h" | |
16 #include "net/base/backoff_entry.h" | |
17 #include "url/gurl.h" | |
18 | |
19 namespace policy { | |
20 | |
21 namespace { | |
22 | |
23 // Policies for exponential backoff of failed requests. There are 3 policies for | |
24 // different classes of errors. | |
25 | |
26 // For temporary errors (HTTP 500, RST, etc). | |
27 const net::BackoffEntry::Policy kRetrySoonPolicy = { | |
28 // Number of initial errors to ignore before starting to back off. | |
29 0, | |
30 | |
31 // Initial delay in ms: 60 seconds. | |
32 1000 * 60, | |
33 | |
34 // Factor by which the waiting time is multiplied. | |
35 2, | |
36 | |
37 // Fuzzing percentage; this spreads delays randomly between 80% and 100% | |
38 // of the calculated time. | |
39 0.20, | |
40 | |
41 // Maximum delay in ms: 12 hours. | |
42 1000 * 60 * 60 * 12, | |
43 | |
44 // When to discard an entry: never. | |
45 -1, | |
46 | |
47 // |always_use_initial_delay|; false means that the initial delay is | |
48 // applied after the first error, and starts backing off from there. | |
49 false, | |
50 }; | |
51 | |
52 // For other errors (request failed, server errors). | |
53 const net::BackoffEntry::Policy kRetryLaterPolicy = { | |
54 // Number of initial errors to ignore before starting to back off. | |
55 0, | |
56 | |
57 // Initial delay in ms: 1 hour. | |
58 1000 * 60 * 60, | |
59 | |
60 // Factor by which the waiting time is multiplied. | |
61 2, | |
62 | |
63 // Fuzzing percentage; this spreads delays randomly between 80% and 100% | |
64 // of the calculated time. | |
65 0.20, | |
66 | |
67 // Maximum delay in ms: 12 hours. | |
68 1000 * 60 * 60 * 12, | |
69 | |
70 // When to discard an entry: never. | |
71 -1, | |
72 | |
73 // |always_use_initial_delay|; false means that the initial delay is | |
74 // applied after the first error, and starts backing off from there. | |
75 false, | |
76 }; | |
77 | |
78 // When the data fails validation (maybe because the policy URL and the data | |
79 // served at that URL are out of sync). This essentially retries every 12 hours, | |
80 // with some random jitter. | |
81 const net::BackoffEntry::Policy kRetryMuchLaterPolicy = { | |
82 // Number of initial errors to ignore before starting to back off. | |
83 0, | |
84 | |
85 // Initial delay in ms: 12 hours. | |
86 1000 * 60 * 60 * 12, | |
87 | |
88 // Factor by which the waiting time is multiplied. | |
89 2, | |
90 | |
91 // Fuzzing percentage; this spreads delays randomly between 80% and 100% | |
92 // of the calculated time. | |
93 0.20, | |
94 | |
95 // Maximum delay in ms: 12 hours. | |
96 1000 * 60 * 60 * 12, | |
97 | |
98 // When to discard an entry: never. | |
99 -1, | |
100 | |
101 // |always_use_initial_delay|; false means that the initial delay is | |
102 // applied after the first error, and starts backing off from there. | |
103 false, | |
104 }; | |
105 | |
106 // Maximum number of retries for requests that aren't likely to get a | |
107 // different response (e.g. HTTP 4xx replies). | |
108 const int kMaxLimitedRetries = 3; | |
109 | |
110 } // namespace | |
111 | |
112 class ExternalPolicyDataUpdater::FetchJob | |
113 : public base::SupportsWeakPtr<FetchJob> { | |
114 public: | |
115 FetchJob(ExternalPolicyDataUpdater* updater, | |
116 const std::string& key, | |
117 const ExternalPolicyDataUpdater::Request& request, | |
118 const ExternalPolicyDataUpdater::FetchSuccessCallback& callback); | |
119 virtual ~FetchJob(); | |
120 | |
121 const std::string& key() const; | |
122 const ExternalPolicyDataUpdater::Request& request() const; | |
123 | |
124 void Start(); | |
125 | |
126 void OnFetchFinished(ExternalPolicyDataFetcher::Result result, | |
127 scoped_ptr<std::string> data); | |
128 | |
129 private: | |
130 void OnFailed(net::BackoffEntry* backoff_entry); | |
131 void Reschedule(); | |
132 | |
133 // Always valid as long as |this| is alive. | |
134 ExternalPolicyDataUpdater* updater_; | |
135 | |
136 const std::string key_; | |
137 const ExternalPolicyDataUpdater::Request request_; | |
138 ExternalPolicyDataUpdater::FetchSuccessCallback callback_; | |
139 | |
140 // If the job is currently running, a corresponding |fetch_job_| exists in the | |
141 // |external_policy_data_fetcher_|. The job must eventually call back to the | |
142 // |updater_|'s OnJobSucceeded() or OnJobFailed() method in this case. | |
143 // If the job is currently not running, |fetch_job_| is NULL and no callbacks | |
144 // should be invoked. | |
145 ExternalPolicyDataFetcher::Job* fetch_job_; // Not owned. | |
146 | |
147 // Some errors should trigger a limited number of retries, even with backoff. | |
148 // This counts down the number of such retries to stop retrying once the limit | |
149 // is reached. | |
150 int limited_retries_remaining_; | |
151 | |
152 // Various delays to retry a failed download, depending on the failure reason. | |
153 net::BackoffEntry retry_soon_entry_; | |
154 net::BackoffEntry retry_later_entry_; | |
155 net::BackoffEntry retry_much_later_entry_; | |
156 | |
157 DISALLOW_COPY_AND_ASSIGN(FetchJob); | |
158 }; | |
159 | |
160 ExternalPolicyDataUpdater::Request::Request() { | |
161 } | |
162 | |
163 ExternalPolicyDataUpdater::Request::Request(const std::string& url, | |
164 const std::string& hash, | |
165 int64 max_size) | |
166 : url(url), hash(hash), max_size(max_size) { | |
167 } | |
168 | |
169 bool ExternalPolicyDataUpdater::Request::operator==( | |
170 const Request& other) const { | |
171 return url == other.url && hash == other.hash && max_size == other.max_size; | |
172 } | |
173 | |
174 ExternalPolicyDataUpdater::FetchJob::FetchJob( | |
175 ExternalPolicyDataUpdater* updater, | |
176 const std::string& key, | |
177 const ExternalPolicyDataUpdater::Request& request, | |
178 const ExternalPolicyDataUpdater::FetchSuccessCallback& callback) | |
179 : updater_(updater), | |
180 key_(key), | |
181 request_(request), | |
182 callback_(callback), | |
183 fetch_job_(NULL), | |
184 limited_retries_remaining_(kMaxLimitedRetries), | |
185 retry_soon_entry_(&kRetrySoonPolicy), | |
186 retry_later_entry_(&kRetryLaterPolicy), | |
187 retry_much_later_entry_(&kRetryMuchLaterPolicy) { | |
188 } | |
189 | |
190 ExternalPolicyDataUpdater::FetchJob::~FetchJob() { | |
191 if (fetch_job_) { | |
192 // Cancel the fetch job in the |external_policy_data_fetcher_|. | |
193 updater_->external_policy_data_fetcher_->CancelJob(fetch_job_); | |
194 // Inform the |updater_| that the job was canceled. | |
195 updater_->OnJobFailed(this); | |
196 } | |
197 } | |
198 | |
199 const std::string& ExternalPolicyDataUpdater::FetchJob::key() const { | |
200 return key_; | |
201 } | |
202 | |
203 const ExternalPolicyDataUpdater::Request& | |
204 ExternalPolicyDataUpdater::FetchJob::request() const { | |
205 return request_; | |
206 } | |
207 | |
208 void ExternalPolicyDataUpdater::FetchJob::Start() { | |
209 DCHECK(!fetch_job_); | |
210 // Start a fetch job in the |external_policy_data_fetcher_|. This will | |
211 // eventually call back to OnFetchFinished() with the result. | |
212 fetch_job_ = updater_->external_policy_data_fetcher_->StartJob( | |
213 GURL(request_.url), request_.max_size, | |
214 base::Bind(&ExternalPolicyDataUpdater::FetchJob::OnFetchFinished, | |
215 base::Unretained(this))); | |
216 } | |
217 | |
218 void ExternalPolicyDataUpdater::FetchJob::OnFetchFinished( | |
219 ExternalPolicyDataFetcher::Result result, | |
220 scoped_ptr<std::string> data) { | |
221 // The fetch job in the |external_policy_data_fetcher_| is finished. | |
222 fetch_job_ = NULL; | |
223 | |
224 switch (result) { | |
225 case ExternalPolicyDataFetcher::CONNECTION_INTERRUPTED: | |
226 // The connection was interrupted. Try again soon. | |
227 OnFailed(&retry_soon_entry_); | |
228 return; | |
229 case ExternalPolicyDataFetcher::NETWORK_ERROR: | |
230 // Another network error occurred. Try again later. | |
231 OnFailed(&retry_later_entry_); | |
232 return; | |
233 case ExternalPolicyDataFetcher::SERVER_ERROR: | |
234 // Problem at the server. Try again soon. | |
235 OnFailed(&retry_soon_entry_); | |
236 return; | |
237 case ExternalPolicyDataFetcher::CLIENT_ERROR: | |
238 // Client error. This is unlikely to go away. Try again later, and give up | |
239 // retrying after 3 attempts. | |
240 OnFailed(limited_retries_remaining_ ? &retry_later_entry_ : NULL); | |
241 if (limited_retries_remaining_) | |
242 --limited_retries_remaining_; | |
243 return; | |
244 case ExternalPolicyDataFetcher::HTTP_ERROR: | |
245 // Any other type of HTTP failure. Try again later. | |
246 OnFailed(&retry_later_entry_); | |
247 return; | |
248 case ExternalPolicyDataFetcher::MAX_SIZE_EXCEEDED: | |
249 // Received |data| exceeds maximum allowed size. This may be because the | |
250 // data being served is stale. Try again much later. | |
251 OnFailed(&retry_much_later_entry_); | |
252 return; | |
253 case ExternalPolicyDataFetcher::SUCCESS: | |
254 break; | |
255 } | |
256 | |
257 if (base::SHA1HashString(*data) != request_.hash) { | |
258 // Received |data| does not match expected hash. This may be because the | |
259 // data being served is stale. Try again much later. | |
260 OnFailed(&retry_much_later_entry_); | |
261 return; | |
262 } | |
263 | |
264 // If the callback rejects the data, try again much later. | |
265 if (!callback_.Run(*data)) { | |
266 OnFailed(&retry_much_later_entry_); | |
267 return; | |
268 } | |
269 | |
270 // Signal success. | |
271 updater_->OnJobSucceeded(this); | |
272 } | |
273 | |
274 void ExternalPolicyDataUpdater::FetchJob::OnFailed(net::BackoffEntry* entry) { | |
275 if (entry) { | |
276 entry->InformOfRequest(false); | |
277 | |
278 // This function may have been invoked because the job was obsoleted and is | |
279 // in the process of being deleted. If this is the case, the WeakPtr will | |
280 // become invalid and the delayed task will never run. | |
281 updater_->task_runner_->PostDelayedTask( | |
282 FROM_HERE, | |
283 base::Bind(&FetchJob::Reschedule, AsWeakPtr()), | |
284 entry->GetTimeUntilRelease()); | |
285 } | |
286 | |
287 updater_->OnJobFailed(this); | |
288 } | |
289 | |
290 void ExternalPolicyDataUpdater::FetchJob::Reschedule() { | |
291 updater_->ScheduleJob(this); | |
292 } | |
293 | |
294 ExternalPolicyDataUpdater::ExternalPolicyDataUpdater( | |
295 scoped_refptr<base::SequencedTaskRunner> task_runner, | |
296 scoped_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher, | |
297 size_t max_parallel_fetches) | |
298 : task_runner_(task_runner), | |
299 external_policy_data_fetcher_(external_policy_data_fetcher.release()), | |
300 max_parallel_jobs_(max_parallel_fetches), | |
301 running_jobs_(0), | |
302 shutting_down_(false) { | |
303 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
304 } | |
305 | |
306 ExternalPolicyDataUpdater::~ExternalPolicyDataUpdater() { | |
307 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
308 shutting_down_ = true; | |
309 STLDeleteValues(&job_map_); | |
310 } | |
311 | |
312 void ExternalPolicyDataUpdater::FetchExternalData( | |
313 const std::string key, | |
314 const Request& request, | |
315 const FetchSuccessCallback& callback) { | |
316 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
317 | |
318 // Check whether a job exists for this |key| already. | |
319 FetchJob* job = job_map_[key]; | |
320 if (job) { | |
321 // If the current |job| is handling the given |request| already, nothing | |
322 // needs to be done. | |
323 if (job->request() == request) | |
324 return; | |
325 | |
326 // Otherwise, the current |job| is obsolete. If the |job| is on the queue, | |
327 // its WeakPtr will be invalidated and skipped by StartNextJobs(). If |job| | |
328 // is currently running, it will call OnJobFailed() immediately. | |
329 delete job; | |
330 job_map_.erase(key); | |
331 } | |
332 | |
333 // Start a new job to handle |request|. | |
334 job = new FetchJob(this, key, request, callback); | |
335 job_map_[key] = job; | |
336 ScheduleJob(job); | |
337 } | |
338 | |
339 void ExternalPolicyDataUpdater::CancelExternalDataFetch( | |
340 const std::string& key) { | |
341 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
342 | |
343 // If a |job| exists for this |key|, delete it. If the |job| is on the queue, | |
344 // its WeakPtr will be invalidated and skipped by StartNextJobs(). If |job| is | |
345 // currently running, it will call OnJobFailed() immediately. | |
346 std::map<std::string, FetchJob*>::iterator job = job_map_.find(key); | |
347 if (job != job_map_.end()) { | |
348 delete job->second; | |
349 job_map_.erase(job); | |
350 } | |
351 } | |
352 | |
353 void ExternalPolicyDataUpdater::StartNextJobs() { | |
354 if (shutting_down_) | |
355 return; | |
356 | |
357 while (running_jobs_ < max_parallel_jobs_ && !job_queue_.empty()) { | |
358 FetchJob* job = job_queue_.front().get(); | |
359 job_queue_.pop(); | |
360 | |
361 // Some of the jobs may have been invalidated, and have to be skipped. | |
362 if (job) { | |
363 ++running_jobs_; | |
364 // A started job will always call OnJobSucceeded() or OnJobFailed(). | |
365 job->Start(); | |
366 } | |
367 } | |
368 } | |
369 | |
370 void ExternalPolicyDataUpdater::ScheduleJob(FetchJob* job) { | |
371 DCHECK_EQ(job_map_[job->key()], job); | |
372 | |
373 job_queue_.push(job->AsWeakPtr()); | |
374 | |
375 StartNextJobs(); | |
376 } | |
377 | |
378 void ExternalPolicyDataUpdater::OnJobSucceeded(FetchJob* job) { | |
379 DCHECK(running_jobs_); | |
380 DCHECK_EQ(job_map_[job->key()], job); | |
381 | |
382 --running_jobs_; | |
383 job_map_.erase(job->key()); | |
384 delete job; | |
385 | |
386 StartNextJobs(); | |
387 } | |
388 | |
389 void ExternalPolicyDataUpdater::OnJobFailed(FetchJob* job) { | |
390 DCHECK(running_jobs_); | |
391 DCHECK_EQ(job_map_[job->key()], job); | |
392 | |
393 --running_jobs_; | |
394 | |
395 // The job is not deleted when it fails because a retry attempt may have been | |
396 // scheduled. | |
397 StartNextJobs(); | |
398 } | |
399 | |
400 } // namespace policy | |
OLD | NEW |