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.
5 #include "chrome/browser/policy/cloud/external_policy_data_updater.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"
23 // Policies for exponential backoff of failed requests. There are 3 policies for
24 // different classes of errors.
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.
31 // Initial delay in ms: 60 seconds.
34 // Factor by which the waiting time is multiplied.
37 // Fuzzing percentage; this spreads delays randomly between 80% and 100%
38 // of the calculated time.
41 // Maximum delay in ms: 12 hours.
44 // When to discard an entry: never.
47 // |always_use_initial_delay|; false means that the initial delay is
48 // applied after the first error, and starts backing off from there.
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.
57 // Initial delay in ms: 1 hour.
60 // Factor by which the waiting time is multiplied.
63 // Fuzzing percentage; this spreads delays randomly between 80% and 100%
64 // of the calculated time.
67 // Maximum delay in ms: 12 hours.
70 // When to discard an entry: never.
73 // |always_use_initial_delay|; false means that the initial delay is
74 // applied after the first error, and starts backing off from there.
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.
85 // Initial delay in ms: 12 hours.
88 // Factor by which the waiting time is multiplied.
91 // Fuzzing percentage; this spreads delays randomly between 80% and 100%
92 // of the calculated time.
95 // Maximum delay in ms: 12 hours.
98 // When to discard an entry: never.
101 // |always_use_initial_delay|; false means that the initial delay is
102 // applied after the first error, and starts backing off from there.
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;
112 class ExternalPolicyDataUpdater::FetchJob
113 : public base::SupportsWeakPtr<FetchJob> {
115 FetchJob(ExternalPolicyDataUpdater* updater,
116 const std::string& key,
117 const ExternalPolicyDataUpdater::Request& request,
118 const ExternalPolicyDataUpdater::FetchSuccessCallback& callback);
121 const std::string& key() const;
122 const ExternalPolicyDataUpdater::Request& request() const;
126 void OnFetchFinished(ExternalPolicyDataFetcher::Result result,
127 scoped_ptr<std::string> data);
130 void OnFailed(net::BackoffEntry* backoff_entry);
133 // Always valid as long as |this| is alive.
134 ExternalPolicyDataUpdater* updater_;
136 const std::string key_;
137 const ExternalPolicyDataUpdater::Request request_;
138 ExternalPolicyDataUpdater::FetchSuccessCallback callback_;
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.
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
150 int limited_retries_remaining_;
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_;
157 DISALLOW_COPY_AND_ASSIGN(FetchJob);
160 ExternalPolicyDataUpdater::Request::Request() {
163 ExternalPolicyDataUpdater::Request::Request(const std::string& url,
164 const std::string& hash,
166 : url(url), hash(hash), max_size(max_size) {
169 bool ExternalPolicyDataUpdater::Request::operator==(
170 const Request& other) const {
171 return url == other.url && hash == other.hash && max_size == other.max_size;
174 ExternalPolicyDataUpdater::FetchJob::FetchJob(
175 ExternalPolicyDataUpdater* updater,
176 const std::string& key,
177 const ExternalPolicyDataUpdater::Request& request,
178 const ExternalPolicyDataUpdater::FetchSuccessCallback& callback)
184 limited_retries_remaining_(kMaxLimitedRetries),
185 retry_soon_entry_(&kRetrySoonPolicy),
186 retry_later_entry_(&kRetryLaterPolicy),
187 retry_much_later_entry_(&kRetryMuchLaterPolicy) {
190 ExternalPolicyDataUpdater::FetchJob::~FetchJob() {
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);
199 const std::string& ExternalPolicyDataUpdater::FetchJob::key() const {
203 const ExternalPolicyDataUpdater::Request&
204 ExternalPolicyDataUpdater::FetchJob::request() const {
208 void ExternalPolicyDataUpdater::FetchJob::Start() {
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)));
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.
225 case ExternalPolicyDataFetcher::CONNECTION_INTERRUPTED:
226 // The connection was interrupted. Try again soon.
227 OnFailed(&retry_soon_entry_);
229 case ExternalPolicyDataFetcher::NETWORK_ERROR:
230 // Another network error occurred. Try again later.
231 OnFailed(&retry_later_entry_);
233 case ExternalPolicyDataFetcher::SERVER_ERROR:
234 // Problem at the server. Try again soon.
235 OnFailed(&retry_soon_entry_);
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_;
244 case ExternalPolicyDataFetcher::HTTP_ERROR:
245 // Any other type of HTTP failure. Try again later.
246 OnFailed(&retry_later_entry_);
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_);
253 case ExternalPolicyDataFetcher::SUCCESS:
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_);
264 // If the callback rejects the data, try again much later.
265 if (!callback_.Run(*data)) {
266 OnFailed(&retry_much_later_entry_);
271 updater_->OnJobSucceeded(this);
274 void ExternalPolicyDataUpdater::FetchJob::OnFailed(net::BackoffEntry* entry) {
276 entry->InformOfRequest(false);
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(
283 base::Bind(&FetchJob::Reschedule, AsWeakPtr()),
284 entry->GetTimeUntilRelease());
287 updater_->OnJobFailed(this);
290 void ExternalPolicyDataUpdater::FetchJob::Reschedule() {
291 updater_->ScheduleJob(this);
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),
302 shutting_down_(false) {
303 DCHECK(task_runner_->RunsTasksOnCurrentThread());
306 ExternalPolicyDataUpdater::~ExternalPolicyDataUpdater() {
307 DCHECK(task_runner_->RunsTasksOnCurrentThread());
308 shutting_down_ = true;
309 STLDeleteValues(&job_map_);
312 void ExternalPolicyDataUpdater::FetchExternalData(
313 const std::string key,
314 const Request& request,
315 const FetchSuccessCallback& callback) {
316 DCHECK(task_runner_->RunsTasksOnCurrentThread());
318 // Check whether a job exists for this |key| already.
319 FetchJob* job = job_map_[key];
321 // If the current |job| is handling the given |request| already, nothing
323 if (job->request() == request)
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.
333 // Start a new job to handle |request|.
334 job = new FetchJob(this, key, request, callback);
339 void ExternalPolicyDataUpdater::CancelExternalDataFetch(
340 const std::string& key) {
341 DCHECK(task_runner_->RunsTasksOnCurrentThread());
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()) {
353 void ExternalPolicyDataUpdater::StartNextJobs() {
357 while (running_jobs_ < max_parallel_jobs_ && !job_queue_.empty()) {
358 FetchJob* job = job_queue_.front().get();
361 // Some of the jobs may have been invalidated, and have to be skipped.
364 // A started job will always call OnJobSucceeded() or OnJobFailed().
370 void ExternalPolicyDataUpdater::ScheduleJob(FetchJob* job) {
371 DCHECK_EQ(job_map_[job->key()], job);
373 job_queue_.push(job->AsWeakPtr());
378 void ExternalPolicyDataUpdater::OnJobSucceeded(FetchJob* job) {
379 DCHECK(running_jobs_);
380 DCHECK_EQ(job_map_[job->key()], job);
383 job_map_.erase(job->key());
389 void ExternalPolicyDataUpdater::OnJobFailed(FetchJob* job) {
390 DCHECK(running_jobs_);
391 DCHECK_EQ(job_map_[job->key()], job);
395 // The job is not deleted when it fails because a retry attempt may have been
400 } // namespace policy