blob: 669cf260bd7bc981e07e29a41b18d3ec60806faa [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/engine/backoff_delay_provider.h"
#include <algorithm>
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/rand_util.h"
#include "components/sync/engine/cycle/model_neutral_state.h"
#include "components/sync/engine/polling_constants.h"
#include "components/sync/engine/sync_protocol_error.h"
#include "components/sync/engine/syncer_error.h"
namespace syncer {
namespace {
// If enabled, the initial delay on network error will be 1 second instead of
// 30 seconds.
BASE_FEATURE(kSyncShortInitialDelayOnNetworkError,
"SyncShortInitialDelayOnNetworkError",
base::FEATURE_ENABLED_BY_DEFAULT);
// This calculates approx. last_delay * kBackoffMultiplyFactor +/- last_delay
// * kBackoffJitterFactor. `jitter_sign` must be -1 or 1 and determines whether
// the jitter in the delay will be positive or negative.
base::TimeDelta GetDelayImpl(base::TimeDelta last_delay, int jitter_sign) {
DCHECK(jitter_sign == -1 || jitter_sign == 1);
if (last_delay >= kMaxBackoffTime) {
return kMaxBackoffTime;
}
const base::TimeDelta backoff =
std::max(kMinBackoffTime, last_delay * kBackoffMultiplyFactor) +
jitter_sign * kBackoffJitterFactor * last_delay;
// Clamp backoff between 1 second and `kMaxBackoffTime`.
return std::max(kMinBackoffTime, std::min(backoff, kMaxBackoffTime));
}
} // namespace
// static
std::unique_ptr<BackoffDelayProvider> BackoffDelayProvider::FromDefaults() {
// base::WrapUnique() used because the constructor is private.
if (base::FeatureList::IsEnabled(kSyncShortInitialDelayOnNetworkError)) {
return base::WrapUnique(new BackoffDelayProvider(
kInitialBackoffRetryTime, kInitialBackoffShortRetryTime));
}
return base::WrapUnique(new BackoffDelayProvider(
kInitialBackoffRetryTime, kInitialBackoffImmediateRetryTime));
}
// static
std::unique_ptr<BackoffDelayProvider>
BackoffDelayProvider::WithShortInitialRetryOverride() {
// base::WrapUnique() used because the constructor is private.
return base::WrapUnique(new BackoffDelayProvider(
kInitialBackoffShortRetryTime, kInitialBackoffImmediateRetryTime));
}
BackoffDelayProvider::BackoffDelayProvider(
const base::TimeDelta& default_initial_backoff,
const base::TimeDelta& short_initial_backoff)
: default_initial_backoff_(default_initial_backoff),
short_initial_backoff_(short_initial_backoff) {}
BackoffDelayProvider::~BackoffDelayProvider() = default;
base::TimeDelta BackoffDelayProvider::GetDelay(
const base::TimeDelta& last_delay) {
// Flip a coin to randomize backoff interval by +/- kBackoffJitterFactor.
const int jitter_sign = base::RandInt(0, 1) * 2 - 1;
return GetDelayImpl(last_delay, jitter_sign);
}
base::TimeDelta BackoffDelayProvider::GetInitialDelay(
const ModelNeutralState& state) const {
// kNetworkError implies we did not receive HTTP response from server because
// of some network error. If network is unavailable then on next connection
// type or address change scheduler will run canary job. Otherwise we'll retry
// in 30 seconds.
if (state.commit_result.type() == SyncerError::Type::kNetworkError ||
state.last_download_updates_result.type() ==
SyncerError::Type::kNetworkError) {
if (base::FeatureList::IsEnabled(kSyncShortInitialDelayOnNetworkError)) {
// Retry with a short delay on network error. This should be safe because
// it shouldn't increase traffic to the server but should help to recover
// from the network errors faster.
return short_initial_backoff_;
}
return default_initial_backoff_;
}
if (state.last_get_key_failed) {
return default_initial_backoff_;
}
// Note: If we received a MIGRATION_DONE on download updates, then commit
// should not have taken place. Moreover, if we receive a MIGRATION_DONE
// on commit, it means that download updates succeeded. Therefore, we only
// need to check if either code is equal to SERVER_RETURN_MIGRATION_DONE,
// and not if there were any more serious errors requiring the long retry.
if (state.last_download_updates_result.type() ==
SyncerError::Type::kProtocolError &&
state.last_download_updates_result.GetProtocolErrorOrDie() ==
SyncProtocolErrorType::MIGRATION_DONE) {
return kInitialBackoffImmediateRetryTime;
}
if (state.commit_result.type() == SyncerError::Type::kProtocolError &&
state.commit_result.GetProtocolErrorOrDie() ==
SyncProtocolErrorType::MIGRATION_DONE) {
return kInitialBackoffImmediateRetryTime;
}
// When the server tells us we have a conflict, then we should download the
// latest updates so we can see the conflict ourselves, resolve it locally,
// then try again to commit. Running another sync cycle will do all these
// things. There's no need to back off, we can do this immediately.
//
// TODO(sync): We shouldn't need to handle this in BackoffDelayProvider.
// There should be a way to deal with protocol errors before we get to this
// point.
if (state.commit_result.type() == SyncerError::Type::kProtocolError &&
state.commit_result.GetProtocolErrorOrDie() ==
SyncProtocolErrorType::CONFLICT) {
return kInitialBackoffImmediateRetryTime;
}
return default_initial_backoff_;
}
base::TimeDelta BackoffDelayProvider::GetDelayForTesting(
base::TimeDelta last_delay,
int jitter_sign) {
return GetDelayImpl(last_delay, jitter_sign);
}
} // namespace syncer