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