| // 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 |