| 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 |