Home | History | Annotate | Download | only in engine
      1 // Copyright (c) 2012 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 "sync/engine/backoff_delay_provider.h"
      6 
      7 #include "base/rand_util.h"
      8 #include "sync/internal_api/public/engine/polling_constants.h"
      9 #include "sync/internal_api/public/sessions/model_neutral_state.h"
     10 #include "sync/internal_api/public/util/syncer_error.h"
     11 
     12 using base::TimeDelta;
     13 
     14 namespace syncer {
     15 
     16 // static
     17 BackoffDelayProvider* BackoffDelayProvider::FromDefaults() {
     18   return new BackoffDelayProvider(
     19       TimeDelta::FromSeconds(kInitialBackoffRetrySeconds),
     20       TimeDelta::FromSeconds(kInitialBackoffImmediateRetrySeconds));
     21 }
     22 
     23 // static
     24 BackoffDelayProvider* BackoffDelayProvider::WithShortInitialRetryOverride() {
     25   return new BackoffDelayProvider(
     26       TimeDelta::FromSeconds(kInitialBackoffShortRetrySeconds),
     27       TimeDelta::FromSeconds(kInitialBackoffImmediateRetrySeconds));
     28 }
     29 
     30 BackoffDelayProvider::BackoffDelayProvider(
     31     const base::TimeDelta& default_initial_backoff,
     32     const base::TimeDelta& short_initial_backoff)
     33     : default_initial_backoff_(default_initial_backoff),
     34       short_initial_backoff_(short_initial_backoff) {
     35 }
     36 
     37 BackoffDelayProvider::~BackoffDelayProvider() {}
     38 
     39 TimeDelta BackoffDelayProvider::GetDelay(const base::TimeDelta& last_delay) {
     40   if (last_delay.InSeconds() >= kMaxBackoffSeconds)
     41     return TimeDelta::FromSeconds(kMaxBackoffSeconds);
     42 
     43   // This calculates approx. base_delay_seconds * 2 +/- base_delay_seconds / 2
     44   int64 backoff_s =
     45       std::max(static_cast<int64>(1),
     46                last_delay.InSeconds() * kBackoffRandomizationFactor);
     47 
     48   // Flip a coin to randomize backoff interval by +/- 50%.
     49   int rand_sign = base::RandInt(0, 1) * 2 - 1;
     50 
     51   // Truncation is adequate for rounding here.
     52   backoff_s = backoff_s +
     53       (rand_sign * (last_delay.InSeconds() / kBackoffRandomizationFactor));
     54 
     55   // Cap the backoff interval.
     56   backoff_s = std::max(static_cast<int64>(1),
     57                        std::min(backoff_s, kMaxBackoffSeconds));
     58 
     59   return TimeDelta::FromSeconds(backoff_s);
     60 }
     61 
     62 TimeDelta BackoffDelayProvider::GetInitialDelay(
     63     const sessions::ModelNeutralState& state) const {
     64   // NETWORK_CONNECTION_UNAVAILABLE implies we did not even manage to hit the
     65   // wire; the failure occurred locally. Note that if commit_result is *not*
     66   // UNSET, this implies download_updates_result succeeded.  Also note that
     67   // last_get_key_result is coupled to last_download_updates_result in that
     68   // they are part of the same GetUpdates request, so we only check if
     69   // the download request is CONNECTION_UNAVAILABLE.
     70   //
     71   // TODO(tim): Should we treat NETWORK_IO_ERROR similarly? It's different
     72   // from CONNECTION_UNAVAILABLE in that a request may well have succeeded
     73   // in contacting the server (e.g we got a 200 back), but we failed
     74   // trying to parse the response (actual content length != HTTP response
     75   // header content length value).  For now since we're considering
     76   // merging this code to branches and I haven't audited all the
     77   // NETWORK_IO_ERROR cases carefully, I'm going to target the fix
     78   // very tightly (see bug chromium-os:35073). DIRECTORY_LOOKUP_FAILED is
     79   // another example of something that shouldn't backoff, though the
     80   // scheduler should probably be handling these cases differently. See
     81   // the TODO(rlarocque) in ScheduleNextSync.
     82   if (state.commit_result == NETWORK_CONNECTION_UNAVAILABLE ||
     83       state.last_download_updates_result == NETWORK_CONNECTION_UNAVAILABLE) {
     84     return short_initial_backoff_;
     85   }
     86 
     87   if (SyncerErrorIsError(state.last_get_key_result))
     88     return default_initial_backoff_;
     89 
     90   // Note: If we received a MIGRATION_DONE on download updates, then commit
     91   // should not have taken place.  Moreover, if we receive a MIGRATION_DONE
     92   // on commit, it means that download updates succeeded.  Therefore, we only
     93   // need to check if either code is equal to SERVER_RETURN_MIGRATION_DONE,
     94   // and not if there were any more serious errors requiring the long retry.
     95   if (state.last_download_updates_result == SERVER_RETURN_MIGRATION_DONE ||
     96       state.commit_result == SERVER_RETURN_MIGRATION_DONE) {
     97     return short_initial_backoff_;
     98   }
     99 
    100   // When the server tells us we have a conflict, then we should download the
    101   // latest updates so we can see the conflict ourselves, resolve it locally,
    102   // then try again to commit.  Running another sync cycle will do all these
    103   // things.  There's no need to back off, we can do this immediately.
    104   //
    105   // TODO(sync): We shouldn't need to handle this in BackoffDelayProvider.
    106   // There should be a way to deal with protocol errors before we get to this
    107   // point.
    108   if (state.commit_result == SERVER_RETURN_CONFLICT) {
    109     return short_initial_backoff_;
    110   }
    111 
    112   return default_initial_backoff_;
    113 }
    114 
    115 }  // namespace syncer
    116