Home | History | Annotate | Download | only in cloud
      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 "components/policy/core/common/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/stl_util.h"
     14 #include "components/policy/core/common/cloud/external_policy_data_fetcher.h"
     15 #include "crypto/sha2.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 (crypto::SHA256HashString(*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
    401