// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/base/backoff_entry.h" #include <algorithm> #include <cmath> #include "base/logging.h" #include "base/rand_util.h" namespace net { BackoffEntry::BackoffEntry(const BackoffEntry::Policy* const policy) : failure_count_(0), policy_(policy) { DCHECK(policy_); // Can't use GetTimeNow() as it's virtual. exponential_backoff_release_time_ = base::TimeTicks::Now(); } BackoffEntry::~BackoffEntry() { // TODO(joi): Remove this once our clients (e.g. URLRequestThrottlerManager) // always destroy from the I/O thread. DetachFromThread(); } void BackoffEntry::InformOfRequest(bool succeeded) { if (!succeeded) { ++failure_count_; exponential_backoff_release_time_ = CalculateReleaseTime(); } else { // We slowly decay the number of times delayed instead of resetting it to 0 // in order to stay stable if we receive successes interleaved between lots // of failures. // // TODO(joi): Revisit this; it might be most correct to go to zero // but have a way to go back to "old error count +1" if there is // another error soon after. if (failure_count_ > 0) --failure_count_; // The reason why we are not just cutting the release time to GetTimeNow() // is on the one hand, it would unset a release time set by // SetCustomReleaseTime and on the other we would like to push every // request up to our "horizon" when dealing with multiple in-flight // requests. Ex: If we send three requests and we receive 2 failures and // 1 success. The success that follows those failures will not reset the // release time, further requests will then need to wait the delay caused // by the 2 failures. exponential_backoff_release_time_ = std::max( GetTimeNow(), exponential_backoff_release_time_); } } bool BackoffEntry::ShouldRejectRequest() const { return exponential_backoff_release_time_ > GetTimeNow(); } base::TimeTicks BackoffEntry::GetReleaseTime() const { return exponential_backoff_release_time_; } void BackoffEntry::SetCustomReleaseTime(const base::TimeTicks& release_time) { exponential_backoff_release_time_ = release_time; } bool BackoffEntry::CanDiscard() const { if (policy_->entry_lifetime_ms == -1) return false; base::TimeTicks now = GetTimeNow(); int64 unused_since_ms = (now - exponential_backoff_release_time_).InMilliseconds(); // Release time is further than now, we are managing it. if (unused_since_ms < 0) return false; if (failure_count_ > 0) { // Need to keep track of failures until maximum back-off period // has passed (since further failures can add to back-off). return unused_since_ms >= std::max(policy_->maximum_backoff_ms, policy_->entry_lifetime_ms); } // Otherwise, consider the entry is outdated if it hasn't been used for the // specified lifetime period. return unused_since_ms >= policy_->entry_lifetime_ms; } base::TimeTicks BackoffEntry::GetTimeNow() const { return base::TimeTicks::Now(); } base::TimeTicks BackoffEntry::CalculateReleaseTime() const { int effective_failure_count = std::max(0, failure_count_ - policy_->num_errors_to_ignore); if (effective_failure_count == 0) { // Never reduce previously set release horizon, e.g. due to Retry-After // header. return std::max(GetTimeNow(), exponential_backoff_release_time_); } // The delay is calculated with this formula: // delay = initial_backoff * multiply_factor^( // effective_failure_count - 1) * Uniform(1 - jitter_factor, 1] double delay = policy_->initial_backoff_ms; delay *= pow(policy_->multiply_factor, effective_failure_count - 1); delay -= base::RandDouble() * policy_->jitter_factor * delay; // Ensure that we do not exceed maximum delay. int64 delay_int = static_cast<int64>(delay + 0.5); delay_int = std::min(delay_int, static_cast<int64>(policy_->maximum_backoff_ms)); // Never reduce previously set release horizon, e.g. due to Retry-After // header. return std::max(GetTimeNow() + base::TimeDelta::FromMilliseconds(delay_int), exponential_backoff_release_time_); } } // namespace net