libweave: Move backoff_entry from libchromeos into libweave
It's used by libweave only.
BUG=brillo:1257
TEST=`FEATURES=test emerge-gizmo libchromeos libweave`
Change-Id: I07aa30a9b758161bf29c8bce7a057550f565dc94
Reviewed-on: https://chromium-review.googlesource.com/293421
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
Tested-by: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/libweave/libweave.gyp b/libweave/libweave.gyp
index 05fc5ca..e0287d7 100644
--- a/libweave/libweave.gyp
+++ b/libweave/libweave.gyp
@@ -27,6 +27,7 @@
'target_name': 'libweave_common',
'type': 'static_library',
'sources': [
+ 'src/backoff_entry.cc',
'src/base_api_handler.cc',
'src/buffet_config.cc',
'src/commands/cloud_command_proxy.cc',
@@ -119,6 +120,7 @@
'external/crypto/p224_spake_unittest.cc',
'external/crypto/p224_unittest.cc',
'external/crypto/sha2_unittest.cc',
+ 'src/backoff_entry_unittest.cc',
'src/base_api_handler_unittest.cc',
'src/buffet_config_unittest.cc',
'src/commands/cloud_command_proxy_unittest.cc',
diff --git a/libweave/src/backoff_entry.cc b/libweave/src/backoff_entry.cc
new file mode 100644
index 0000000..b9c5b8f
--- /dev/null
+++ b/libweave/src/backoff_entry.cc
@@ -0,0 +1,167 @@
+// Copyright 2015 The Chromium OS 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 "libweave/src/backoff_entry.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include <base/logging.h>
+#include <base/numerics/safe_math.h>
+#include <base/rand_util.h>
+
+namespace weave {
+
+BackoffEntry::BackoffEntry(const BackoffEntry::Policy* const policy)
+ : policy_(policy) {
+ DCHECK(policy_);
+ Reset();
+}
+
+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. Note that in
+ // the normal case, the calculated release time (in the next
+ // statement) will be in the past once the method returns.
+ if (failure_count_ > 0)
+ --failure_count_;
+
+ // The reason why we are not just cutting the release time to
+ // ImplGetTimeNow() 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.
+ base::TimeDelta delay;
+ if (policy_->always_use_initial_delay)
+ delay = base::TimeDelta::FromMilliseconds(policy_->initial_delay_ms);
+ exponential_backoff_release_time_ =
+ std::max(ImplGetTimeNow() + delay, exponential_backoff_release_time_);
+ }
+}
+
+bool BackoffEntry::ShouldRejectRequest() const {
+ return exponential_backoff_release_time_ > ImplGetTimeNow();
+}
+
+base::TimeDelta BackoffEntry::GetTimeUntilRelease() const {
+ base::TimeTicks now = ImplGetTimeNow();
+ if (exponential_backoff_release_time_ <= now)
+ return base::TimeDelta();
+ return exponential_backoff_release_time_ - now;
+}
+
+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 = ImplGetTimeNow();
+
+ 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;
+}
+
+void BackoffEntry::Reset() {
+ failure_count_ = 0;
+
+ // We leave exponential_backoff_release_time_ unset, meaning 0. We could
+ // initialize to ImplGetTimeNow() but because it's a virtual method it's
+ // not safe to call in the constructor (and the constructor calls Reset()).
+ // The effects are the same, i.e. ShouldRejectRequest() will return false
+ // right after Reset().
+ exponential_backoff_release_time_ = base::TimeTicks();
+}
+
+base::TimeTicks BackoffEntry::ImplGetTimeNow() 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 always_use_initial_delay is true, it's equivalent to
+ // the effective_failure_count always being one greater than when it's false.
+ if (policy_->always_use_initial_delay)
+ ++effective_failure_count;
+
+ if (effective_failure_count == 0) {
+ // Never reduce previously set release horizon, e.g. due to Retry-After
+ // header.
+ return std::max(ImplGetTimeNow(), 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]
+ // Note: if the failure count is too high, |delay_ms| will become infinity
+ // after the exponential calculation, and then NaN after the jitter is
+ // accounted for. Both cases are handled by using CheckedNumeric<int64_t> to
+ // perform the conversion to integers.
+ double delay_ms = policy_->initial_delay_ms;
+ delay_ms *= pow(policy_->multiply_factor, effective_failure_count - 1);
+ delay_ms -= base::RandDouble() * policy_->jitter_factor * delay_ms;
+
+ // Do overflow checking in microseconds, the internal unit of TimeTicks.
+ const int64_t kTimeTicksNowUs =
+ (ImplGetTimeNow() - base::TimeTicks()).InMicroseconds();
+ base::internal::CheckedNumeric<int64_t> calculated_release_time_us =
+ delay_ms + 0.5;
+ calculated_release_time_us *= base::Time::kMicrosecondsPerMillisecond;
+ calculated_release_time_us += kTimeTicksNowUs;
+
+ const int64_t kMaxTime = std::numeric_limits<int64_t>::max();
+ base::internal::CheckedNumeric<int64_t> maximum_release_time_us = kMaxTime;
+ if (policy_->maximum_backoff_ms >= 0) {
+ maximum_release_time_us = policy_->maximum_backoff_ms;
+ maximum_release_time_us *= base::Time::kMicrosecondsPerMillisecond;
+ maximum_release_time_us += kTimeTicksNowUs;
+ }
+
+ // Decide between maximum release time and calculated release time, accounting
+ // for overflow with both.
+ int64 release_time_us =
+ std::min(calculated_release_time_us.ValueOrDefault(kMaxTime),
+ maximum_release_time_us.ValueOrDefault(kMaxTime));
+
+ // Never reduce previously set release horizon, e.g. due to Retry-After
+ // header.
+ return std::max(
+ base::TimeTicks() + base::TimeDelta::FromMicroseconds(release_time_us),
+ exponential_backoff_release_time_);
+}
+
+} // namespace weave
diff --git a/libweave/src/backoff_entry.h b/libweave/src/backoff_entry.h
new file mode 100644
index 0000000..3e7a036
--- /dev/null
+++ b/libweave/src/backoff_entry.h
@@ -0,0 +1,114 @@
+// Copyright 2015 The Chromium OS 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 LIBWEAVE_SRC_BACKOFF_ENTRY_H_
+#define LIBWEAVE_SRC_BACKOFF_ENTRY_H_
+
+#include <base/time/time.h>
+
+namespace weave {
+
+// Provides the core logic needed for randomized exponential back-off
+// on requests to a given resource, given a back-off policy.
+//
+// This class is largely taken from net/base/backoff_entry.h from Chromium.
+// TODO(avakulenko): Consider packaging portions of Chrome's //net functionality
+// into the current libchrome library.
+class BackoffEntry {
+ 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. The interpretation of this value depends on
+ // always_use_initial_delay. It's either how long we wait between
+ // requests before backoff starts, or how much we delay the first request
+ // after backoff starts.
+ int initial_delay_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, -1
+ // for no maximum.
+ int64 maximum_backoff_ms;
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ int64 entry_lifetime_ms;
+
+ // If true, we always use a delay of initial_delay_ms, even before
+ // we've seen num_errors_to_ignore errors. Otherwise, initial_delay_ms
+ // is the first delay once we start exponential backoff.
+ //
+ // So if we're ignoring 1 error, we'll see (N, N, Nm, Nm^2, ...) if true,
+ // and (0, 0, N, Nm, ...) when false, where N is initial_backoff_ms and
+ // m is multiply_factor, assuming we've already seen one success.
+ bool always_use_initial_delay;
+ };
+
+ // 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() = default;
+
+ // 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;
+
+ // Returns the time until a request can be sent.
+ base::TimeDelta GetTimeUntilRelease() 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;
+
+ // Resets this entry to a fresh (as if just constructed) state.
+ void Reset();
+
+ // Returns the failure count for this entry.
+ int failure_count() const { return failure_count_; }
+
+ protected:
+ // Equivalent to TimeTicks::Now(), virtual so unit tests can override.
+ virtual base::TimeTicks ImplGetTimeNow() 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; decremented on success.
+ int failure_count_;
+
+ const Policy* const policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackoffEntry);
+};
+
+} // namespace weave
+
+#endif // LIBWEAVE_SRC_BACKOFF_ENTRY_H_
diff --git a/libweave/src/backoff_entry_unittest.cc b/libweave/src/backoff_entry_unittest.cc
new file mode 100644
index 0000000..5b198a0
--- /dev/null
+++ b/libweave/src/backoff_entry_unittest.cc
@@ -0,0 +1,309 @@
+// Copyright 2015 The Chromium OS 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 "libweave/src/backoff_entry.h"
+
+#include <gtest/gtest.h>
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace weave {
+
+BackoffEntry::Policy base_policy = {0, 1000, 2.0, 0.0, 20000, 2000, false};
+
+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());
+ }
+
+ ~TestBackoffEntry() override {}
+
+ TimeTicks ImplGetTimeNow() const override { 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());
+ EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease());
+
+ entry.InformOfRequest(false);
+ EXPECT_TRUE(entry.ShouldRejectRequest());
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+}
+
+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, CanDiscardAlwaysDelay) {
+ BackoffEntry::Policy always_delay_policy = base_policy;
+ always_delay_policy.always_use_initial_delay = true;
+ always_delay_policy.entry_lifetime_ms = 0;
+
+ TestBackoffEntry entry(&always_delay_policy);
+
+ // Because lifetime is non-zero, we shouldn't be able to discard yet.
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000));
+ EXPECT_TRUE(entry.CanDiscard());
+
+ // Even with no failures, we wait until the delay before we allow discard.
+ entry.InformOfRequest(true);
+ EXPECT_FALSE(entry.CanDiscard());
+
+ // Wait until the delay expires, and we can discard the entry again.
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1000));
+ 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.ImplGetTimeNow(), result);
+
+ // 1 error.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(1000), result);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // 2 errors.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(2000), result);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+
+ // 3 errors.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease());
+
+ // 6 errors (to check it doesn't pass maximum).
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(20000),
+ result);
+}
+
+TEST(BackoffEntryTest, ReleaseTimeCalculationAlwaysDelay) {
+ BackoffEntry::Policy always_delay_policy = base_policy;
+ always_delay_policy.always_use_initial_delay = true;
+ always_delay_policy.num_errors_to_ignore = 2;
+
+ TestBackoffEntry entry(&always_delay_policy);
+
+ // With previous requests, should return "now".
+ TimeTicks result = entry.GetReleaseTime();
+ EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease());
+
+ // 1 error.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // 2 errors.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // 3 errors, exponential backoff starts.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+
+ // 4 errors.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease());
+
+ // 8 errors (to check it doesn't pass maximum).
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(TimeDelta::FromMilliseconds(20000), entry.GetTimeUntilRelease());
+}
+
+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.ImplGetTimeNow() + TimeDelta::FromMilliseconds(3200),
+ result);
+ EXPECT_GE(entry.ImplGetTimeNow() + 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, FailureThenSuccessAlwaysDelay) {
+ BackoffEntry::Policy always_delay_policy = base_policy;
+ always_delay_policy.always_use_initial_delay = true;
+ always_delay_policy.num_errors_to_ignore = 1;
+
+ TestBackoffEntry entry(&always_delay_policy);
+
+ // Failure count 1.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // Failure count 2.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000));
+
+ // Success. We should go back to the original delay.
+ entry.InformOfRequest(true);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // Failure count reaches 2 again. We should increase the delay once more.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000));
+}
+
+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());
+}
+
+TEST(BackoffEntryTest, OverflowProtection) {
+ BackoffEntry::Policy large_multiply_policy = base_policy;
+ large_multiply_policy.multiply_factor = 256;
+ TestBackoffEntry custom(&large_multiply_policy);
+
+ // Trigger enough failures such that more than 11 bits of exponent are used
+ // to represent the exponential backoff intermediate values. Given a multiply
+ // factor of 256 (2^8), 129 iterations is enough: 2^(8*(129-1)) = 2^1024.
+ for (int i = 0; i < 129; ++i) {
+ custom.set_now(custom.ImplGetTimeNow() + custom.GetTimeUntilRelease());
+ custom.InformOfRequest(false);
+ ASSERT_TRUE(custom.ShouldRejectRequest());
+ }
+
+ // Max delay should still be respected.
+ EXPECT_EQ(20000, custom.GetTimeUntilRelease().InMilliseconds());
+}
+
+} // namespace weave
diff --git a/libweave/src/commands/cloud_command_proxy.cc b/libweave/src/commands/cloud_command_proxy.cc
index 58da7d1..d38fa04 100644
--- a/libweave/src/commands/cloud_command_proxy.cc
+++ b/libweave/src/commands/cloud_command_proxy.cc
@@ -19,7 +19,7 @@
CommandInstance* command_instance,
CloudCommandUpdateInterface* cloud_command_updater,
StateChangeQueueInterface* state_change_queue,
- std::unique_ptr<chromeos::BackoffEntry> backoff_entry,
+ std::unique_ptr<BackoffEntry> backoff_entry,
TaskRunner* task_runner)
: command_instance_{command_instance},
cloud_command_updater_{cloud_command_updater},
diff --git a/libweave/src/commands/cloud_command_proxy.h b/libweave/src/commands/cloud_command_proxy.h
index 838b1ff..98a7c6c 100644
--- a/libweave/src/commands/cloud_command_proxy.h
+++ b/libweave/src/commands/cloud_command_proxy.h
@@ -13,10 +13,10 @@
#include <base/macros.h>
#include <base/memory/weak_ptr.h>
#include <base/scoped_observer.h>
-#include <chromeos/backoff_entry.h>
#include <weave/command.h>
#include <weave/task_runner.h>
+#include "libweave/src/backoff_entry.h"
#include "libweave/src/commands/cloud_command_update_interface.h"
#include "libweave/src/states/state_change_queue_interface.h"
@@ -30,7 +30,7 @@
CloudCommandProxy(CommandInstance* command_instance,
CloudCommandUpdateInterface* cloud_command_updater,
StateChangeQueueInterface* state_change_queue,
- std::unique_ptr<chromeos::BackoffEntry> backoff_entry,
+ std::unique_ptr<BackoffEntry> backoff_entry,
TaskRunner* task_runner);
~CloudCommandProxy() override = default;
@@ -73,7 +73,7 @@
TaskRunner* task_runner_{nullptr};
// Backoff for SendCommandUpdate() method.
- std::unique_ptr<chromeos::BackoffEntry> cloud_backoff_entry_;
+ std::unique_ptr<BackoffEntry> cloud_backoff_entry_;
// Set to true while a pending PATCH request is in flight to the server.
bool command_update_in_progress_{false};
diff --git a/libweave/src/commands/cloud_command_proxy_unittest.cc b/libweave/src/commands/cloud_command_proxy_unittest.cc
index 8bdbfd9..d98776a 100644
--- a/libweave/src/commands/cloud_command_proxy_unittest.cc
+++ b/libweave/src/commands/cloud_command_proxy_unittest.cc
@@ -58,15 +58,15 @@
};
// Test back-off entry that uses the test clock.
-class TestBackoffEntry : public chromeos::BackoffEntry {
+class TestBackoffEntry : public BackoffEntry {
public:
TestBackoffEntry(const Policy* const policy, base::Clock* clock)
- : chromeos::BackoffEntry{policy}, clock_{clock} {
+ : BackoffEntry{policy}, clock_{clock} {
creation_time_ = clock->Now();
}
private:
- // Override from chromeos::BackoffEntry to use the custom test clock for
+ // Override from BackoffEntry to use the custom test clock for
// the backoff calculations.
base::TimeTicks ImplGetTimeNow() const override {
return base::TimeTicks::FromInternalValue(clock_->Now().ToInternalValue());
@@ -143,8 +143,8 @@
CHECK(command_instance_.get());
// Backoff - start at 1s and double with each backoff attempt and no jitter.
- static const chromeos::BackoffEntry::Policy policy{
- 0, 1000, 2.0, 0.0, 20000, -1, false};
+ static const BackoffEntry::Policy policy{0, 1000, 2.0, 0.0,
+ 20000, -1, false};
std::unique_ptr<TestBackoffEntry> backoff{
new TestBackoffEntry{&policy, &clock_}};
diff --git a/libweave/src/device_registration_info.cc b/libweave/src/device_registration_info.cc
index f648337..bc738f6 100644
--- a/libweave/src/device_registration_info.cc
+++ b/libweave/src/device_registration_info.cc
@@ -222,7 +222,7 @@
config_{std::move(config)},
notifications_enabled_{notifications_enabled},
network_{network} {
- cloud_backoff_policy_.reset(new chromeos::BackoffEntry::Policy{});
+ cloud_backoff_policy_.reset(new BackoffEntry::Policy{});
cloud_backoff_policy_->num_errors_to_ignore = 0;
cloud_backoff_policy_->initial_delay_ms = 1000;
cloud_backoff_policy_->multiply_factor = 2.0;
@@ -230,10 +230,8 @@
cloud_backoff_policy_->maximum_backoff_ms = 30000;
cloud_backoff_policy_->entry_lifetime_ms = -1;
cloud_backoff_policy_->always_use_initial_delay = false;
- cloud_backoff_entry_.reset(
- new chromeos::BackoffEntry{cloud_backoff_policy_.get()});
- oauth2_backoff_entry_.reset(
- new chromeos::BackoffEntry{cloud_backoff_policy_.get()});
+ cloud_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
+ oauth2_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
command_manager_->AddOnCommandDefChanged(
base::Bind(&DeviceRegistrationInfo::OnCommandDefsChanged,
@@ -1102,8 +1100,8 @@
if (!command_manager_->FindCommand(command_instance->GetID())) {
LOG(INFO) << "New command '" << command_instance->GetName()
<< "' arrived, ID: " << command_instance->GetID();
- std::unique_ptr<chromeos::BackoffEntry> backoff_entry{
- new chromeos::BackoffEntry{cloud_backoff_policy_.get()}};
+ std::unique_ptr<BackoffEntry> backoff_entry{
+ new BackoffEntry{cloud_backoff_policy_.get()}};
std::unique_ptr<CloudCommandProxy> cloud_proxy{new CloudCommandProxy{
command_instance.get(), this, state_manager_->GetStateChangeQueue(),
std::move(backoff_entry), task_runner_}};
diff --git a/libweave/src/device_registration_info.h b/libweave/src/device_registration_info.h
index 62f2b65..fefc27c 100644
--- a/libweave/src/device_registration_info.h
+++ b/libweave/src/device_registration_info.h
@@ -18,7 +18,6 @@
#include <base/single_thread_task_runner.h>
#include <base/time/time.h>
#include <base/timer/timer.h>
-#include <chromeos/backoff_entry.h>
#include <chromeos/data_encoding.h>
#include <chromeos/errors/error.h>
#include <weave/cloud.h>
@@ -26,6 +25,7 @@
#include <weave/http_client.h>
#include <weave/task_runner.h>
+#include "libweave/src/backoff_entry.h"
#include "libweave/src/buffet_config.h"
#include "libweave/src/commands/cloud_command_update_interface.h"
#include "libweave/src/commands/command_manager.h"
@@ -311,9 +311,9 @@
std::unique_ptr<BuffetConfig> config_;
// Backoff manager for DoCloudRequest() method.
- std::unique_ptr<chromeos::BackoffEntry::Policy> cloud_backoff_policy_;
- std::unique_ptr<chromeos::BackoffEntry> cloud_backoff_entry_;
- std::unique_ptr<chromeos::BackoffEntry> oauth2_backoff_entry_;
+ std::unique_ptr<BackoffEntry::Policy> cloud_backoff_policy_;
+ std::unique_ptr<BackoffEntry> cloud_backoff_entry_;
+ std::unique_ptr<BackoffEntry> oauth2_backoff_entry_;
// Flag set to true while a device state update patch request is in flight
// to the cloud server.
diff --git a/libweave/src/notification/xmpp_channel.cc b/libweave/src/notification/xmpp_channel.cc
index b5aa425..7d0d5c6 100644
--- a/libweave/src/notification/xmpp_channel.cc
+++ b/libweave/src/notification/xmpp_channel.cc
@@ -7,11 +7,11 @@
#include <string>
#include <base/bind.h>
-#include <chromeos/backoff_entry.h>
#include <chromeos/data_encoding.h>
#include <weave/network.h>
#include <weave/task_runner.h>
+#include "libweave/src/backoff_entry.h"
#include "libweave/src/notification/notification_delegate.h"
#include "libweave/src/notification/notification_parser.h"
#include "libweave/src/notification/xml_node.h"
@@ -47,7 +47,7 @@
// Backoff policy.
// Note: In order to ensure a minimum of 20 seconds between server errors,
// we have a 30s +- 10s (33%) jitter initial backoff.
-const chromeos::BackoffEntry::Policy kDefaultBackoffPolicy = {
+const BackoffEntry::Policy kDefaultBackoffPolicy = {
// Number of initial errors (in sequence) to ignore before applying
// exponential back-off rules.
0,
diff --git a/libweave/src/notification/xmpp_channel.h b/libweave/src/notification/xmpp_channel.h
index 1e42c2c..d6ae529 100644
--- a/libweave/src/notification/xmpp_channel.h
+++ b/libweave/src/notification/xmpp_channel.h
@@ -13,9 +13,9 @@
#include <base/callback_forward.h>
#include <base/macros.h>
#include <base/memory/weak_ptr.h>
-#include <chromeos/backoff_entry.h>
#include <weave/stream.h>
+#include "libweave/src/backoff_entry.h"
#include "libweave/src/notification/notification_channel.h"
#include "libweave/src/notification/xmpp_iq_stanza_handler.h"
#include "libweave/src/notification/xmpp_stream_parser.h"
@@ -152,7 +152,7 @@
std::string host_;
uint16_t port_{0};
- chromeos::BackoffEntry backoff_entry_;
+ BackoffEntry backoff_entry_;
NotificationDelegate* delegate_{nullptr};
TaskRunner* task_runner_{nullptr};
XmppStreamParser stream_parser_{this};