summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/base/backoff_entry.cc119
-rw-r--r--net/base/backoff_entry.h94
-rw-r--r--net/base/backoff_entry_unittest.cc203
-rw-r--r--net/net.gyp3
-rw-r--r--net/url_request/url_request_throttler_entry.cc152
-rw-r--r--net/url_request/url_request_throttler_entry.h48
-rw-r--r--net/url_request/url_request_throttler_entry_interface.h4
-rw-r--r--net/url_request/url_request_throttler_header_adapter.h1
-rw-r--r--net/url_request/url_request_throttler_manager.h1
-rw-r--r--net/url_request/url_request_throttler_unittest.cc109
10 files changed, 526 insertions, 208 deletions
diff --git a/net/base/backoff_entry.cc b/net/base/backoff_entry.cc
new file mode 100644
index 0000000..830eca4
--- /dev/null
+++ b/net/base/backoff_entry.cc
@@ -0,0 +1,119 @@
+// 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; at the moment we may be destroyed
+ // from the main thread (during AtExit processing).
+ DetachFromThread();
+}
+
+void BackoffEntry::InformOfRequest(bool succeeded) {
+ if (!succeeded) {
+ failure_count_++;
+ exponential_backoff_release_time_ = CalculateReleaseTime();
+ } else {
+ failure_count_ = 0;
+
+ // 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
diff --git a/net/base/backoff_entry.h b/net/base/backoff_entry.h
new file mode 100644
index 0000000..6cf164d
--- /dev/null
+++ b/net/base/backoff_entry.h
@@ -0,0 +1,94 @@
+// 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.
+
+#ifndef NET_BASE_BACKOFF_ITEM_H_
+#define NET_BASE_BACKOFF_ITEM_H_
+#pragma once
+
+#include "base/threading/non_thread_safe.h"
+#include "base/time.h"
+
+namespace net {
+
+// Provides the core logic needed for randomized exponential back-off
+// on requests to a given resource, given a back-off policy.
+//
+// This utility class knows nothing about network specifics; it is
+// intended for reuse in various networking scenarios.
+class BackoffEntry : public base::NonThreadSafe {
+ public:
+ // The set of parameters that define a back-off policy.
+ struct Policy {
+ // Number of initial errors (in sequence) to ignore before applying
+ // exponential back-off rules.
+ int num_errors_to_ignore;
+
+ // Initial delay for exponential back-off.
+ int initial_backoff_ms;
+
+ // Factor by which the waiting time will be multiplied.
+ double multiply_factor;
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ double jitter_factor;
+
+ // Maximum amount of time we are willing to delay our request.
+ int maximum_backoff_ms;
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ int entry_lifetime_ms;
+ };
+
+ // Lifetime of policy must enclose lifetime of BackoffEntry. The
+ // pointer must be valid but is not dereferenced during construction.
+ explicit BackoffEntry(const Policy* const policy);
+ virtual ~BackoffEntry();
+
+ // Inform this item that a request for the network resource it is
+ // tracking was made, and whether it failed or succeeded.
+ void InformOfRequest(bool succeeded);
+
+ // Returns true if a request for the resource this item tracks should
+ // be rejected at the present time due to exponential back-off policy.
+ bool ShouldRejectRequest() const;
+
+ // Returns the absolute time after which this entry (given its present
+ // state) will no longer reject requests.
+ base::TimeTicks GetReleaseTime() const;
+
+ // Causes this object reject requests until the specified absolute time.
+ // This can be used to e.g. implement support for a Retry-After header.
+ void SetCustomReleaseTime(const base::TimeTicks& release_time);
+
+ // Returns true if this object has no significant state (i.e. you could
+ // just as well start with a fresh BackoffEntry object), and hasn't
+ // had for Policy::entry_lifetime_ms_.
+ bool CanDiscard() const;
+
+ protected:
+ // Equivalent to TimeTicks::Now(), virtual so unit tests can override.
+ // TODO(joi): Switch to constructor-time dependency injection?
+ virtual base::TimeTicks GetTimeNow() const;
+
+ private:
+ // Calculates when requests should again be allowed through.
+ base::TimeTicks CalculateReleaseTime() const;
+
+ // Timestamp calculated by the exponential back-off algorithm at which we are
+ // allowed to start sending requests again.
+ base::TimeTicks exponential_backoff_release_time_;
+
+ // Counts request errors; reset on success.
+ int failure_count_;
+
+ const Policy* const policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackoffEntry);
+};
+
+} // namespace net
+
+#endif // NET_BASE_BACKOFF_ITEM_H_
diff --git a/net/base/backoff_entry_unittest.cc b/net/base/backoff_entry_unittest.cc
new file mode 100644
index 0000000..cad40a6
--- /dev/null
+++ b/net/base/backoff_entry_unittest.cc
@@ -0,0 +1,203 @@
+// 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 "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using base::TimeDelta;
+using base::TimeTicks;
+using net::BackoffEntry;
+
+BackoffEntry::Policy base_policy = { 0, 1000, 2.0, 0.0, 20000, 2000 };
+
+class TestBackoffEntry : public BackoffEntry {
+ public:
+ explicit TestBackoffEntry(const Policy* const policy)
+ : BackoffEntry(policy),
+ now_(TimeTicks()) {
+ // Work around initialization in constructor not picking up
+ // fake time.
+ SetCustomReleaseTime(TimeTicks());
+ }
+
+ virtual ~TestBackoffEntry() {}
+
+ virtual TimeTicks GetTimeNow() const {
+ return now_;
+ }
+
+ void set_now(const TimeTicks& now) {
+ now_ = now;
+ }
+
+ private:
+ TimeTicks now_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestBackoffEntry);
+};
+
+TEST(BackoffEntryTest, BaseTest) {
+ TestBackoffEntry entry(&base_policy);
+ EXPECT_FALSE(entry.ShouldRejectRequest());
+
+ entry.InformOfRequest(false);
+ EXPECT_TRUE(entry.ShouldRejectRequest());
+}
+
+TEST(BackoffEntryTest, CanDiscardNeverExpires) {
+ BackoffEntry::Policy never_expires_policy = base_policy;
+ never_expires_policy.entry_lifetime_ms = -1;
+ TestBackoffEntry never_expires(&never_expires_policy);
+ EXPECT_FALSE(never_expires.CanDiscard());
+ never_expires.set_now(TimeTicks() + TimeDelta::FromDays(100));
+ EXPECT_FALSE(never_expires.CanDiscard());
+}
+
+TEST(BackoffEntryTest, CanDiscard) {
+ TestBackoffEntry entry(&base_policy);
+ // Because lifetime is non-zero, we shouldn't be able to discard yet.
+ EXPECT_FALSE(entry.CanDiscard());
+
+ // Test the "being used" case.
+ entry.InformOfRequest(false);
+ EXPECT_FALSE(entry.CanDiscard());
+
+ // Test the case where there are errors but we can time out.
+ entry.set_now(
+ entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1));
+ EXPECT_FALSE(entry.CanDiscard());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(
+ base_policy.maximum_backoff_ms + 1));
+ EXPECT_TRUE(entry.CanDiscard());
+
+ // Test the final case (no errors, dependent only on specified lifetime).
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(
+ base_policy.entry_lifetime_ms - 1));
+ entry.InformOfRequest(true);
+ EXPECT_FALSE(entry.CanDiscard());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(
+ base_policy.entry_lifetime_ms));
+ EXPECT_TRUE(entry.CanDiscard());
+}
+
+TEST(BackoffEntryTest, CanDiscardNotStored) {
+ BackoffEntry::Policy no_store_policy = base_policy;
+ no_store_policy.entry_lifetime_ms = 0;
+ TestBackoffEntry not_stored(&no_store_policy);
+ EXPECT_TRUE(not_stored.CanDiscard());
+}
+
+TEST(BackoffEntryTest, ShouldIgnoreFirstTwo) {
+ BackoffEntry::Policy lenient_policy = base_policy;
+ lenient_policy.num_errors_to_ignore = 2;
+
+ BackoffEntry entry(&lenient_policy);
+ entry.InformOfRequest(false);
+ EXPECT_FALSE(entry.ShouldRejectRequest());
+ entry.InformOfRequest(false);
+ EXPECT_FALSE(entry.ShouldRejectRequest());
+ entry.InformOfRequest(false);
+ EXPECT_TRUE(entry.ShouldRejectRequest());
+}
+
+TEST(BackoffEntryTest, ReleaseTimeCalculation) {
+ TestBackoffEntry entry(&base_policy);
+
+ // With zero errors, should return "now".
+ TimeTicks result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.GetTimeNow(), result);
+
+ // 1 error.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.GetTimeNow() + TimeDelta::FromMilliseconds(1000), result);
+
+ // 2 errors.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.GetTimeNow() + TimeDelta::FromMilliseconds(2000), result);
+
+ // 3 errors.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.GetTimeNow() + TimeDelta::FromMilliseconds(4000), result);
+
+ // 6 errors (to check it doesn't pass maximum).
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.GetTimeNow() + TimeDelta::FromMilliseconds(20000), result);
+}
+
+TEST(BackoffEntryTest, ReleaseTimeCalculationWithJitter) {
+ for (int i = 0; i < 10; ++i) {
+ BackoffEntry::Policy jittery_policy = base_policy;
+ jittery_policy.jitter_factor = 0.2;
+
+ TestBackoffEntry entry(&jittery_policy);
+
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ TimeTicks result = entry.GetReleaseTime();
+ EXPECT_LE(entry.GetTimeNow() + TimeDelta::FromMilliseconds(3200), result);
+ EXPECT_GE(entry.GetTimeNow() + TimeDelta::FromMilliseconds(4000), result);
+ }
+}
+
+TEST(BackoffEntryTest, FailureThenSuccess) {
+ TestBackoffEntry entry(&base_policy);
+
+ // Failure count 1, establishes horizon.
+ entry.InformOfRequest(false);
+ TimeTicks release_time = entry.GetReleaseTime();
+ EXPECT_EQ(TimeTicks() + TimeDelta::FromMilliseconds(1000), release_time);
+
+ // Success, failure count 0, should not advance past
+ // the horizon that was already set.
+ entry.set_now(release_time - TimeDelta::FromMilliseconds(200));
+ entry.InformOfRequest(true);
+ EXPECT_EQ(release_time, entry.GetReleaseTime());
+
+ // Failure, failure count 1.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(release_time + TimeDelta::FromMilliseconds(800),
+ entry.GetReleaseTime());
+}
+
+TEST(BackoffEntryTest, RetainCustomHorizon) {
+ TestBackoffEntry custom(&base_policy);
+ TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3);
+ custom.SetCustomReleaseTime(custom_horizon);
+ custom.InformOfRequest(false);
+ custom.InformOfRequest(true);
+ custom.set_now(TimeTicks() + TimeDelta::FromDays(2));
+ custom.InformOfRequest(false);
+ custom.InformOfRequest(true);
+ EXPECT_EQ(custom_horizon, custom.GetReleaseTime());
+
+ // Now check that once we are at or past the custom horizon,
+ // we get normal behavior.
+ custom.set_now(TimeTicks() + TimeDelta::FromDays(3));
+ custom.InformOfRequest(false);
+ EXPECT_EQ(
+ TimeTicks() + TimeDelta::FromDays(3) + TimeDelta::FromMilliseconds(1000),
+ custom.GetReleaseTime());
+}
+
+TEST(BackoffEntryTest, RetainCustomHorizonWhenInitialErrorsIgnored) {
+ // Regression test for a bug discovered during code review.
+ BackoffEntry::Policy lenient_policy = base_policy;
+ lenient_policy.num_errors_to_ignore = 1;
+ TestBackoffEntry custom(&lenient_policy);
+ TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3);
+ custom.SetCustomReleaseTime(custom_horizon);
+ custom.InformOfRequest(false); // This must not reset the horizon.
+ EXPECT_EQ(custom_horizon, custom.GetReleaseTime());
+}
+
+} // namespace
diff --git a/net/net.gyp b/net/net.gyp
index 5d3ca045..3443c83 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -29,6 +29,8 @@
'base/address_list_net_log_param.h',
'base/auth.cc',
'base/auth.h',
+ 'base/backoff_entry.cc',
+ 'base/backoff_entry.h',
'base/bandwidth_metrics.cc',
'base/bandwidth_metrics.h',
'base/cache_type.h',
@@ -870,6 +872,7 @@
'msvs_guid': 'E99DA267-BE90-4F45-88A1-6919DB2C7567',
'sources': [
'base/address_list_unittest.cc',
+ 'base/backoff_entry_unittest.cc',
'base/cert_database_nss_unittest.cc',
'base/cert_verifier_unittest.cc',
'base/cookie_monster_unittest.cc',
diff --git a/net/url_request/url_request_throttler_entry.cc b/net/url_request/url_request_throttler_entry.cc
index 033787b..05b2af2 100644
--- a/net/url_request/url_request_throttler_entry.cc
+++ b/net/url_request/url_request_throttler_entry.cc
@@ -16,7 +16,6 @@ namespace net {
const int URLRequestThrottlerEntry::kDefaultSlidingWindowPeriodMs = 2000;
const int URLRequestThrottlerEntry::kDefaultMaxSendThreshold = 20;
const int URLRequestThrottlerEntry::kDefaultInitialBackoffMs = 700;
-const int URLRequestThrottlerEntry::kDefaultAdditionalConstantMs = 100;
const double URLRequestThrottlerEntry::kDefaultMultiplyFactor = 1.4;
const double URLRequestThrottlerEntry::kDefaultJitterFactor = 0.4;
const int URLRequestThrottlerEntry::kDefaultMaximumBackoffMs = 60 * 60 * 1000;
@@ -27,12 +26,7 @@ URLRequestThrottlerEntry::URLRequestThrottlerEntry()
: sliding_window_period_(
base::TimeDelta::FromMilliseconds(kDefaultSlidingWindowPeriodMs)),
max_send_threshold_(kDefaultMaxSendThreshold),
- initial_backoff_ms_(kDefaultInitialBackoffMs),
- additional_constant_ms_(kDefaultAdditionalConstantMs),
- multiply_factor_(kDefaultMultiplyFactor),
- jitter_factor_(kDefaultJitterFactor),
- maximum_backoff_ms_(kDefaultMaximumBackoffMs),
- entry_lifetime_ms_(kDefaultEntryLifetimeMs) {
+ backoff_entry_(&backoff_policy_) {
Initialize();
}
@@ -40,77 +34,54 @@ URLRequestThrottlerEntry::URLRequestThrottlerEntry(
int sliding_window_period_ms,
int max_send_threshold,
int initial_backoff_ms,
- int additional_constant_ms,
double multiply_factor,
double jitter_factor,
int maximum_backoff_ms)
: sliding_window_period_(
base::TimeDelta::FromMilliseconds(sliding_window_period_ms)),
max_send_threshold_(max_send_threshold),
- initial_backoff_ms_(initial_backoff_ms),
- additional_constant_ms_(additional_constant_ms),
- multiply_factor_(multiply_factor),
- jitter_factor_(jitter_factor),
- maximum_backoff_ms_(maximum_backoff_ms),
- entry_lifetime_ms_(-1) {
+ backoff_entry_(&backoff_policy_) {
DCHECK_GT(sliding_window_period_ms, 0);
DCHECK_GT(max_send_threshold_, 0);
- DCHECK_GE(initial_backoff_ms_, 0);
- DCHECK_GE(additional_constant_ms_, 0);
- DCHECK_GT(multiply_factor_, 0);
- DCHECK_GE(jitter_factor_, 0);
- DCHECK_LT(jitter_factor_, 1);
- DCHECK_GE(maximum_backoff_ms_, 0);
+ DCHECK_GE(initial_backoff_ms, 0);
+ DCHECK_GT(multiply_factor, 0);
+ DCHECK_GE(jitter_factor, 0.0);
+ DCHECK_LT(jitter_factor, 1.0);
+ DCHECK_GE(maximum_backoff_ms, 0);
Initialize();
+ backoff_policy_.initial_backoff_ms = initial_backoff_ms;
+ backoff_policy_.multiply_factor = multiply_factor;
+ backoff_policy_.jitter_factor = jitter_factor;
+ backoff_policy_.maximum_backoff_ms = maximum_backoff_ms;
+ backoff_policy_.entry_lifetime_ms = -1;
}
bool URLRequestThrottlerEntry::IsEntryOutdated() const {
- CHECK(this); // to help track crbug.com/71721
- if (entry_lifetime_ms_ == -1)
- return false;
-
- base::TimeTicks now = GetTimeNow();
-
// If there are send events in the sliding window period, we still need this
// entry.
if (!send_log_.empty() &&
- send_log_.back() + sliding_window_period_ > now) {
+ send_log_.back() + sliding_window_period_ > GetTimeNow()) {
return false;
}
- 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;
-
- // latest_response_was_failure_ is true indicates that the latest one or
- // more requests encountered server errors or had malformed response bodies.
- // In that case, we don't want to collect the entry unless it hasn't been used
- // for longer than the maximum allowed back-off.
- if (latest_response_was_failure_)
- return unused_since_ms > std::max(maximum_backoff_ms_, entry_lifetime_ms_);
-
- // Otherwise, consider the entry is outdated if it hasn't been used for the
- // specified lifetime period.
- return unused_since_ms > entry_lifetime_ms_;
+ return GetBackoffEntry()->CanDiscard();
}
bool URLRequestThrottlerEntry::IsDuringExponentialBackoff() const {
- return exponential_backoff_release_time_ > GetTimeNow();
+ return GetBackoffEntry()->ShouldRejectRequest();
}
int64 URLRequestThrottlerEntry::ReserveSendingTimeForNextRequest(
const base::TimeTicks& earliest_time) {
base::TimeTicks now = GetTimeNow();
+
// If a lot of requests were successfully made recently,
// sliding_window_release_time_ may be greater than
// exponential_backoff_release_time_.
base::TimeTicks recommended_sending_time =
std::max(std::max(now, earliest_time),
- std::max(exponential_backoff_release_time_,
+ std::max(GetBackoffEntry()->GetReleaseTime(),
sliding_window_release_time_));
DCHECK(send_log_.empty() ||
@@ -138,34 +109,15 @@ int64 URLRequestThrottlerEntry::ReserveSendingTimeForNextRequest(
base::TimeTicks
URLRequestThrottlerEntry::GetExponentialBackoffReleaseTime() const {
- return exponential_backoff_release_time_;
+ return GetBackoffEntry()->GetReleaseTime();
}
void URLRequestThrottlerEntry::UpdateWithResponse(
const URLRequestThrottlerHeaderInterface* response) {
if (response->GetResponseCode() >= 500) {
- failure_count_++;
- latest_response_was_failure_ = true;
- exponential_backoff_release_time_ =
- CalculateExponentialBackoffReleaseTime();
+ GetBackoffEntry()->InformOfRequest(false);
} else {
- // We slowly decay the number of times delayed instead of resetting it to 0
- // in order to stay stable if we received lots of requests with
- // malformed bodies at the same time.
- if (failure_count_ > 0)
- failure_count_--;
-
- latest_response_was_failure_ = false;
-
- // The reason why we are not just cutting the release time to GetTimeNow()
- // is on the one hand, it would unset delay put by our custom retry-after
- // header 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_);
+ GetBackoffEntry()->InformOfRequest(true);
std::string retry_header = response->GetNormalizedValue(kRetryHeaderName);
if (!retry_header.empty())
@@ -174,49 +126,26 @@ void URLRequestThrottlerEntry::UpdateWithResponse(
}
void URLRequestThrottlerEntry::ReceivedContentWasMalformed() {
- // For any response that is marked as malformed now, we have probably
- // considered it as a success when receiving it and decreased the failure
- // count by 1. As a result, we increase the failure count by 2 here to undo
- // the effect and record a failure.
+ // We keep this simple and just count it as a single error.
//
- // Please note that this may lead to a larger failure count than expected,
- // because we don't decrease the failure count for successful responses when
- // it has already reached 0.
- failure_count_ += 2;
- latest_response_was_failure_ = true;
- exponential_backoff_release_time_ = CalculateExponentialBackoffReleaseTime();
-}
-
-void URLRequestThrottlerEntry::SetEntryLifetimeMsForTest(int lifetime_ms) {
- entry_lifetime_ms_ = lifetime_ms;
+ // If we wanted to get fancy, we would count two errors here, and decrease
+ // the error count only by one when we receive a successful (by status
+ // code) response. Instead, we keep things simple by always resetting the
+ // error count on success, and therefore counting only a single error here.
+ GetBackoffEntry()->InformOfRequest(false);
}
URLRequestThrottlerEntry::~URLRequestThrottlerEntry() {
}
void URLRequestThrottlerEntry::Initialize() {
- // Since this method is called by the constructors, GetTimeNow() (a virtual
- // method) is not used.
- exponential_backoff_release_time_ = base::TimeTicks::Now();
- failure_count_ = 0;
- latest_response_was_failure_ = false;
-
sliding_window_release_time_ = base::TimeTicks::Now();
-}
-
-base::TimeTicks
- URLRequestThrottlerEntry::CalculateExponentialBackoffReleaseTime() {
- double delay = initial_backoff_ms_;
- delay *= pow(multiply_factor_, failure_count_);
- delay += additional_constant_ms_;
- delay -= base::RandDouble() * 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>(maximum_backoff_ms_));
-
- return std::max(GetTimeNow() + base::TimeDelta::FromMilliseconds(delay_int),
- exponential_backoff_release_time_);
+ backoff_policy_.num_errors_to_ignore = 0;
+ backoff_policy_.initial_backoff_ms = kDefaultInitialBackoffMs;
+ backoff_policy_.multiply_factor = kDefaultMultiplyFactor;
+ backoff_policy_.jitter_factor = kDefaultJitterFactor;
+ backoff_policy_.maximum_backoff_ms = kDefaultMaximumBackoffMs;
+ backoff_policy_.entry_lifetime_ms = kDefaultEntryLifetimeMs;
}
base::TimeTicks URLRequestThrottlerEntry::GetTimeNow() const {
@@ -236,12 +165,21 @@ void URLRequestThrottlerEntry::HandleCustomRetryAfter(
// We must use an int value later so we transform this in milliseconds.
int64 value_ms = static_cast<int64>(0.5 + time_in_sec * 1000);
- if (maximum_backoff_ms_ < value_ms || value_ms < 0)
+ // We do not check for an upper bound; the server can set any Retry-After it
+ // desires. Recovery from error would involve restarting the browser.
+ if (value_ms < 0)
return;
- exponential_backoff_release_time_ = std::max(
- (GetTimeNow() + base::TimeDelta::FromMilliseconds(value_ms)),
- exponential_backoff_release_time_);
+ GetBackoffEntry()->SetCustomReleaseTime(
+ GetTimeNow() + base::TimeDelta::FromMilliseconds(value_ms));
+}
+
+const BackoffEntry* URLRequestThrottlerEntry::GetBackoffEntry() const {
+ return &backoff_entry_;
+}
+
+BackoffEntry* URLRequestThrottlerEntry::GetBackoffEntry() {
+ return &backoff_entry_;
}
} // namespace net
diff --git a/net/url_request/url_request_throttler_entry.h b/net/url_request/url_request_throttler_entry.h
index cefe8d2..ddea919 100644
--- a/net/url_request/url_request_throttler_entry.h
+++ b/net/url_request/url_request_throttler_entry.h
@@ -4,11 +4,13 @@
#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_H_
#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_H_
+#pragma once
#include <queue>
#include <string>
#include "base/basictypes.h"
+#include "net/base/backoff_entry.h"
#include "net/url_request/url_request_throttler_entry_interface.h"
namespace net {
@@ -35,9 +37,6 @@ class URLRequestThrottlerEntry : public URLRequestThrottlerEntryInterface {
// Initial delay for exponential back-off.
static const int kDefaultInitialBackoffMs;
- // Additional constant to adjust back-off.
- static const int kDefaultAdditionalConstantMs;
-
// Factor by which the waiting time will be multiplied.
static const double kDefaultMultiplyFactor;
@@ -63,7 +62,6 @@ class URLRequestThrottlerEntry : public URLRequestThrottlerEntryInterface {
URLRequestThrottlerEntry(int sliding_window_period_ms,
int max_send_threshold,
int initial_backoff_ms,
- int additional_constant_ms,
double multiply_factor,
double jitter_factor,
int maximum_backoff_ms);
@@ -80,27 +78,22 @@ class URLRequestThrottlerEntry : public URLRequestThrottlerEntryInterface {
virtual void UpdateWithResponse(
const URLRequestThrottlerHeaderInterface* response);
virtual void ReceivedContentWasMalformed();
- virtual void SetEntryLifetimeMsForTest(int lifetime_ms);
protected:
virtual ~URLRequestThrottlerEntry();
void Initialize();
- // Calculates the release time for exponential back-off.
- base::TimeTicks CalculateExponentialBackoffReleaseTime();
-
// Equivalent to TimeTicks::Now(), virtual to be mockable for testing purpose.
virtual base::TimeTicks GetTimeNow() const;
// Used internally to increase release time following a retry-after header.
void HandleCustomRetryAfter(const std::string& header_value);
- // Used by tests.
- void set_exponential_backoff_release_time(
- const base::TimeTicks& release_time) {
- exponential_backoff_release_time_ = release_time;
- }
+ // Retrieves the backoff entry object we're using. Used to enable a
+ // unit testing seam for dependency injection in tests.
+ virtual const BackoffEntry* GetBackoffEntry() const;
+ virtual BackoffEntry* GetBackoffEntry();
// Used by tests.
base::TimeTicks sliding_window_release_time() const {
@@ -112,25 +105,10 @@ class URLRequestThrottlerEntry : public URLRequestThrottlerEntryInterface {
sliding_window_release_time_ = release_time;
}
- // Used by tests.
- void set_failure_count(int failure_count) {
- failure_count_ = failure_count;
- }
+ // Valid and immutable after construction time.
+ BackoffEntry::Policy backoff_policy_;
private:
- // Timestamp calculated by the exponential back-off algorithm at which we are
- // allowed to start sending requests again.
- base::TimeTicks exponential_backoff_release_time_;
-
- // Number of times we encounter server errors or malformed response bodies.
- int failure_count_;
-
- // If true, the last request response was a failure.
- // Note that this member can be false at the same time as failure_count_ can
- // be greater than 0, since we gradually decrease failure_count_, instead of
- // resetting it to 0 directly, when we receive successful responses.
- bool latest_response_was_failure_;
-
// Timestamp calculated by the sliding window algorithm for when we advise
// clients the next request should be made, at the earliest. Advisory only,
// not used to deny requests.
@@ -142,13 +120,9 @@ class URLRequestThrottlerEntry : public URLRequestThrottlerEntryInterface {
const base::TimeDelta sliding_window_period_;
const int max_send_threshold_;
- const int initial_backoff_ms_;
- const int additional_constant_ms_;
- const double multiply_factor_;
- const double jitter_factor_;
- const int maximum_backoff_ms_;
- // Set to -1 if the entry never expires.
- int entry_lifetime_ms_;
+
+ // Access it through GetBackoffEntry() to allow a unit test seam.
+ BackoffEntry backoff_entry_;
DISALLOW_COPY_AND_ASSIGN(URLRequestThrottlerEntry);
};
diff --git a/net/url_request/url_request_throttler_entry_interface.h b/net/url_request/url_request_throttler_entry_interface.h
index e3fc5ba..63dd14c 100644
--- a/net/url_request/url_request_throttler_entry_interface.h
+++ b/net/url_request/url_request_throttler_entry_interface.h
@@ -4,6 +4,7 @@
#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_INTERFACE_H_
#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_INTERFACE_H_
+#pragma once
#include "base/basictypes.h"
#include "base/ref_counted.h"
@@ -50,9 +51,6 @@ class URLRequestThrottlerEntryInterface
// the request, i.e. it will count as a failure.
virtual void ReceivedContentWasMalformed() = 0;
- // For unit testing only.
- virtual void SetEntryLifetimeMsForTest(int lifetime_ms) = 0;
-
protected:
friend class base::RefCountedThreadSafe<URLRequestThrottlerEntryInterface>;
virtual ~URLRequestThrottlerEntryInterface() {}
diff --git a/net/url_request/url_request_throttler_header_adapter.h b/net/url_request/url_request_throttler_header_adapter.h
index 35d363e..a360747 100644
--- a/net/url_request/url_request_throttler_header_adapter.h
+++ b/net/url_request/url_request_throttler_header_adapter.h
@@ -4,6 +4,7 @@
#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_ADAPTER_H_
#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_ADAPTER_H_
+#pragma once
#include <string>
diff --git a/net/url_request/url_request_throttler_manager.h b/net/url_request/url_request_throttler_manager.h
index d1ede1d..1cd2b5c 100644
--- a/net/url_request/url_request_throttler_manager.h
+++ b/net/url_request/url_request_throttler_manager.h
@@ -4,6 +4,7 @@
#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_MANAGER_H_
#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_MANAGER_H_
+#pragma once
#include <map>
#include <string>
diff --git a/net/url_request/url_request_throttler_unittest.cc b/net/url_request/url_request_throttler_unittest.cc
index 7d4e739..4aed38c 100644
--- a/net/url_request/url_request_throttler_unittest.cc
+++ b/net/url_request/url_request_throttler_unittest.cc
@@ -19,23 +19,61 @@ using base::TimeTicks;
namespace {
class MockURLRequestThrottlerManager;
+class MockBackoffEntry : public net::BackoffEntry {
+ public:
+ explicit MockBackoffEntry(const net::BackoffEntry::Policy* const policy)
+ : net::BackoffEntry(policy), fake_now_(TimeTicks()) {
+ }
+
+ virtual ~MockBackoffEntry() {}
+
+ TimeTicks GetTimeNow() const {
+ return fake_now_;
+ }
+
+ void SetFakeNow(TimeTicks now) {
+ fake_now_ = now;
+ }
+
+ private:
+ TimeTicks fake_now_;
+};
+
class MockURLRequestThrottlerEntry : public net::URLRequestThrottlerEntry {
public :
- MockURLRequestThrottlerEntry() {}
+ MockURLRequestThrottlerEntry() : mock_backoff_entry_(&backoff_policy_) {
+ // Some tests become flaky if we have jitter.
+ backoff_policy_.jitter_factor = 0.0;
+ }
MockURLRequestThrottlerEntry(
const TimeTicks& exponential_backoff_release_time,
const TimeTicks& sliding_window_release_time,
const TimeTicks& fake_now)
- : fake_time_now_(fake_now) {
+ : fake_time_now_(fake_now),
+ mock_backoff_entry_(&backoff_policy_) {
+ // Some tests become flaky if we have jitter.
+ backoff_policy_.jitter_factor = 0.0;
+
+ mock_backoff_entry_.SetFakeNow(fake_now);
set_exponential_backoff_release_time(exponential_backoff_release_time);
set_sliding_window_release_time(sliding_window_release_time);
}
virtual ~MockURLRequestThrottlerEntry() {}
+ const net::BackoffEntry* GetBackoffEntry() const {
+ return &mock_backoff_entry_;
+ }
+
+ net::BackoffEntry* GetBackoffEntry() {
+ return &mock_backoff_entry_;
+ }
+
void ResetToBlank(const TimeTicks& time_now) {
fake_time_now_ = time_now;
- set_exponential_backoff_release_time(time_now);
- set_failure_count(0);
+ mock_backoff_entry_.SetFakeNow(time_now);
+
+ GetBackoffEntry()->InformOfRequest(true); // Sets failure count to 0.
+ GetBackoffEntry()->SetCustomReleaseTime(time_now);
set_sliding_window_release_time(time_now);
}
@@ -44,8 +82,7 @@ class MockURLRequestThrottlerEntry : public net::URLRequestThrottlerEntry {
void set_exponential_backoff_release_time(
const base::TimeTicks& release_time) {
- net::URLRequestThrottlerEntry::set_exponential_backoff_release_time(
- release_time);
+ GetBackoffEntry()->SetCustomReleaseTime(release_time);
}
base::TimeTicks sliding_window_release_time() const {
@@ -59,6 +96,7 @@ class MockURLRequestThrottlerEntry : public net::URLRequestThrottlerEntry {
}
TimeTicks fake_time_now_;
+ MockBackoffEntry mock_backoff_entry_;
};
class MockURLRequestThrottlerHeaderAdapter
@@ -226,7 +264,8 @@ TEST_F(URLRequestThrottlerEntryTest, IsEntryReallyOutdated) {
TimeAndBool(now_, false, __LINE__),
TimeAndBool(now_ - kFiveMs, false, __LINE__),
TimeAndBool(now_ + kFiveMs, false, __LINE__),
- TimeAndBool(now_ - lifetime, false, __LINE__),
+ TimeAndBool(now_ - (lifetime - kFiveMs), false, __LINE__),
+ TimeAndBool(now_ - lifetime, true, __LINE__),
TimeAndBool(now_ - (lifetime + kFiveMs), true, __LINE__)};
for (unsigned int i = 0; i < arraysize(test_values); ++i) {
@@ -352,59 +391,7 @@ TEST(URLRequestThrottlerManager, IsHostBeingRegistered) {
EXPECT_EQ(3, manager.GetNumberOfEntries());
}
-class DummyResponseHeaders : public net::URLRequestThrottlerHeaderInterface {
- public:
- DummyResponseHeaders(int response_code) : response_code_(response_code) {}
-
- std::string GetNormalizedValue(const std::string& key) const {
- return "";
- }
-
- // Returns the HTTP response code associated with the request.
- int GetResponseCode() const {
- return response_code_;
- }
-
- private:
- int response_code_;
-};
-
-TEST(URLRequestThrottlerManager, StressTest) {
- MockURLRequestThrottlerManager manager;
-
- for (int i = 0; i < 10000; ++i) {
- // Make some entries duplicates or triplicates.
- int entry_number = i + 2;
- if (i % 17 == 0) {
- entry_number = i - 1;
- } else if ((i - 1) % 17 == 0) {
- entry_number = i - 2;
- } else if (i % 13 == 0) {
- entry_number = i - 1;
- }
-
- scoped_refptr<net::URLRequestThrottlerEntryInterface> entry =
- manager.RegisterRequestUrl(GURL(StringPrintf(
- "http://bingourl.com/%d", entry_number)));
- entry->IsDuringExponentialBackoff();
- entry->GetExponentialBackoffReleaseTime();
-
- DummyResponseHeaders headers(i % 7 == 0 ? 500 : 200);
- entry->UpdateWithResponse(&headers);
- entry->SetEntryLifetimeMsForTest(1);
-
- entry->IsDuringExponentialBackoff();
- entry->GetExponentialBackoffReleaseTime();
- entry->ReserveSendingTimeForNextRequest(base::TimeTicks::Now());
-
- if (i % 11 == 0) {
- manager.DoGarbageCollectEntries();
- }
- }
-}
-
-// TODO(joi): Remove the debug-only condition after M11 branch point.
-#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
+#if defined(GTEST_HAS_DEATH_TEST)
TEST(URLRequestThrottlerManager, NullHandlingTest) {
MockURLRequestThrottlerManager manager;
manager.OverrideEntryForTests(GURL("http://www.foo.com/"), NULL);
@@ -412,4 +399,4 @@ TEST(URLRequestThrottlerManager, NullHandlingTest) {
manager.DoGarbageCollectEntries();
}, "");
}
-#endif // defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
+#endif // defined(GTEST_HAS_DEATH_TEST)