blob: 6ad80ec7f8256b637ad73430c81df4f23673ce0a [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/sync/test/integration/exponential_backoff_helper.h"
#include <algorithm>
#include <ostream>
#include "components/sync/engine/cycle/model_neutral_state.h"
#include "components/sync/engine/cycle/sync_cycle_snapshot.h"
#include "components/sync/engine/polling_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace exponential_backoff_helper {
namespace {
constexpr size_t kMaxRetriesToVerify = 3;
constexpr base::TimeDelta kMinExtraUnexpectedDelayForWarning = base::Seconds(1);
bool DidLastSyncCycleFail(syncer::SyncService* sync_service) {
// Note that SyncCycleSnapshot::is_silenced() is avoided here because,
// unfortunately, it's one cycle "behind". is_silenced() continues to be
// be false upon completion of the first cycle leading to backoff, because the
// sync cycle snapshot is taken *before* the |is_silenced| bit is set to true
// by SyncSchedulerImpl::HandleFailure().
return syncer::HasSyncerError(
sync_service->GetLastCycleSnapshotForDebugging().model_neutral_state());
}
base::TimeDelta ClampBackoffDelay(base::TimeDelta delay) {
return std::clamp(delay, syncer::kMinBackoffTime, syncer::kMaxBackoffTime);
}
} // namespace
// static
ExponentialBackoffChecker::DelayRange
ExponentialBackoffChecker::CalculateDelayRange(base::TimeDelta current_delay) {
// Given the current delay calculate the minimum and maximum wait times for
// each retry. This is analogous to the production logic in
// BackoffDelayProvider::GetDelay().
const base::TimeDelta backoff = std::max(
base::Seconds(1), current_delay * syncer::kBackoffMultiplyFactor);
return {.min_delay = ClampBackoffDelay(
backoff - current_delay * syncer::kBackoffJitterFactor),
.max_delay = ClampBackoffDelay(
backoff + current_delay * syncer::kBackoffJitterFactor)};
}
std::vector<ExponentialBackoffChecker::DelayRange>
ExponentialBackoffChecker::BuildExpectedDelayTable(
base::TimeDelta initial_delay) {
std::vector<DelayRange> delay_table;
delay_table.push_back(CalculateDelayRange(initial_delay));
for (size_t i = 1; i < kMaxRetriesToVerify; ++i) {
DelayRange range;
range.min_delay =
CalculateDelayRange(delay_table.back().min_delay).min_delay;
range.max_delay =
CalculateDelayRange(delay_table.back().max_delay).max_delay;
delay_table.push_back(range);
}
return delay_table;
}
ExponentialBackoffChecker::ExponentialBackoffChecker(
syncer::SyncServiceImpl* sync_service,
base::TimeDelta initial_delay)
: SingleClientStatusChangeChecker(sync_service),
expected_delay_table_(BuildExpectedDelayTable(initial_delay)) {
// Upon construction, backoff must not have started, since it's otherwise
// impossible to determine the precise timestamp corresponding to the first
// backed-off sync cycle, required to predict the exponential behavior.
if (DidLastSyncCycleFail(sync_service)) {
ADD_FAILURE() << "Last sync cycle already failed upon construction of "
<< "ExponentialBackoffChecker.";
}
}
ExponentialBackoffChecker::~ExponentialBackoffChecker() = default;
void ExponentialBackoffChecker::OnSyncCycleCompleted(
syncer::SyncService* sync_service) {
const syncer::SyncCycleSnapshot& snap =
sync_service->GetLastCycleSnapshotForDebugging();
if (!DidLastSyncCycleFail(sync_service)) {
return;
}
// The very first backed-off cycle has itself no delay to verify, but only
// acts as reference point.
if (!last_sync_time_.is_null()) {
// Note that this measures the delay between the *start* time of two cycles,
// instead of measuring the time between one cycle ending and the next one
// starting. However, the difference is negligible when using a fake server,
// because sync cycles are very fast, and either way this checker cannot be
// strict about upper bounds (there may be extra delays for various reasons
// under high CPU load).
actual_delays_.push_back(snap.sync_start_time() - last_sync_time_);
}
last_sync_time_ = snap.sync_start_time();
CheckExitCondition();
}
bool ExponentialBackoffChecker::IsExitConditionSatisfied(std::ostream* os) {
*os << "Verifying backoff intervals " << actual_delays_.size() << " out of "
<< kMaxRetriesToVerify << "\n";
for (size_t i = 0; i < std::min(actual_delays_.size(), kMaxRetriesToVerify);
++i) {
*os << "Delay for retry " << (i + 1) << "/" << kMaxRetriesToVerify
<< " expected between " << expected_delay_table_[i].min_delay
<< " and (approximately) " << expected_delay_table_[i].max_delay
<< "; actual = " << actual_delays_[i] << "\n";
if (actual_delays_[i] < expected_delay_table_[i].min_delay) {
*os << "ERROR: Delay " << i << " is too short\n";
return false;
}
if (actual_delays_[i] > expected_delay_table_[i].max_delay +
kMinExtraUnexpectedDelayForWarning) {
// Although the delay is usually before the max delay, there is nothing
// providing strong guarantees about when precisely the sync thread is
// able to issue a request to the server. Hence, to avoid test flakiness,
// this is treated as a warning only.
*os << "WARNING: delay " << i
<< " is longer than expected, but this may be due to high CPU load\n";
}
}
return actual_delays_.size() >= kMaxRetriesToVerify;
}
} // namespace exponential_backoff_helper