Index: components/password_manager/core/browser/affiliation_fetch_throttler.cc |
diff --git a/components/password_manager/core/browser/affiliation_fetch_throttler.cc b/components/password_manager/core/browser/affiliation_fetch_throttler.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..dbda8d5fc697c8c67b86a4ff2c85aa53f7e3eb4f |
--- /dev/null |
+++ b/components/password_manager/core/browser/affiliation_fetch_throttler.cc |
@@ -0,0 +1,166 @@ |
+// Copyright 2015 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 "components/password_manager/core/browser/affiliation_fetch_throttler.h" |
+ |
+#include <stdint.h> |
+ |
+#include "base/logging.h" |
+#include "base/rand_util.h" |
+#include "base/thread_task_runner_handle.h" |
+#include "base/time/tick_clock.h" |
+#include "base/time/time.h" |
+#include "components/password_manager/core/browser/affiliation_fetch_throttler_delegate.h" |
+#include "net/base/backoff_entry.h" |
+ |
+namespace password_manager { |
+ |
+namespace { |
+ |
+const net::BackoffEntry::Policy kAffiliationBackoffParameters = { |
+ // Number of initial errors (in sequence) to ignore before going into |
+ // exponential backoff. |
+ 0, |
+ |
+ // Initial delay (in ms) once backoff starts. |
+ 10 * 1000, // 10 seconds |
+ |
+ // Factor by which the delay will be multiplied on each subsequent failure. |
+ 4, |
+ |
+ // Fuzzing percentage: 50% will spread delays randomly between 50%--100% of |
+ // the nominal time. |
+ .5, // 50% |
+ |
+ // Maximum delay (in ms) during exponential backoff. |
+ 6 * 3600 * 1000, // 6 hours |
+ |
+ // Time to keep an entry from being discarded even when it has no |
+ // significant state, -1 to never discard. (Not applicable.) |
+ -1, |
+ |
+ // False means that initial_delay_ms is the first delay once we start |
+ // exponential backoff, i.e., there is no delay after subsequent successful |
+ // requests. |
+ false, |
+}; |
+ |
+// Grace period before the first request is sent after network connectivity is |
+// restored. The fuzzing factor from above applies. |
+const int64_t kGracePeriodAfterNetworkRestoredInMs = 10 * 1000; // 10 seconds |
+ |
+// Implementation of net::BackoffEntry that allows mocking its tick source. |
+class BackoffEntryImpl : public net::BackoffEntry { |
+ public: |
+ explicit BackoffEntryImpl(base::TickClock* tick_clock) |
+ : BackoffEntry(&kAffiliationBackoffParameters), tick_clock_(tick_clock) {} |
+ ~BackoffEntryImpl() override {} |
+ |
+ private: |
+ // net::BackoffEntry: |
+ base::TimeTicks ImplGetTimeNow() const override { |
+ return tick_clock_->NowTicks(); |
+ } |
+ |
+ // Must outlive this instance. |
+ base::TickClock* tick_clock_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(BackoffEntryImpl); |
+}; |
+ |
+} // namespace |
+ |
+AffiliationFetchThrottler::AffiliationFetchThrottler( |
+ AffiliationFetchThrottlerDelegate* delegate, |
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
+ scoped_ptr<base::TickClock> tick_clock) |
+ : delegate_(delegate), |
+ task_runner_(task_runner), |
+ state_(IDLE), |
+ has_network_connectivity_(false), |
+ is_fetch_scheduled_(false), |
+ tick_clock_(tick_clock.Pass()), |
+ exponential_backoff_(new BackoffEntryImpl(tick_clock_.get())), |
+ weak_ptr_factory_(this) { |
+ DCHECK(delegate); |
+ net::NetworkChangeNotifier::AddConnectionTypeObserver(this); |
+ has_network_connectivity_ = !net::NetworkChangeNotifier::IsOffline(); |
mmenke
2015/01/16 21:28:34
Think it's worth a comment why this shouldn't be i
engedy
2015/01/19 18:13:11
Done.
|
+} |
+ |
+AffiliationFetchThrottler::~AffiliationFetchThrottler() { |
+ net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); |
+} |
+ |
+void AffiliationFetchThrottler::SignalNetworkRequestNeeded() { |
+ if (state_ != IDLE) |
mmenke
2015/01/16 21:28:35
Should we DCHECK on this? I assume one class will
engedy
2015/01/19 18:13:10
Being able to call this multiple times allows the
|
+ return; |
+ |
+ state_ = FETCH_NEEDED; |
mmenke
2015/01/16 21:28:35
Does the delegate have to be able to handle a call
mmenke
2015/01/16 21:49:54
Hrm...Was thinking there was a case where that cou
engedy
2015/01/19 18:13:11
Yes, I believe this can never happen. I have added
|
+ if (has_network_connectivity_) |
+ EnsureCallbackIsScheduled(); |
+} |
+ |
+void AffiliationFetchThrottler::InformOfNetworkRequestComplete(bool success) { |
+ DCHECK_EQ(state_, FETCH_IN_FLIGHT); |
mmenke
2015/01/16 21:28:34
Suggest making expected value the first argument (
engedy
2015/01/19 18:13:11
I'd prefer to keep it this way. As opposed to EXPE
|
+ state_ = IDLE; |
+ exponential_backoff_->InformOfRequest(success); |
mmenke
2015/01/16 21:31:28
It's up to the consumer to tell us if/when they wa
engedy
2015/01/19 18:13:11
Done. PTAL at the class comment.
|
+} |
+ |
+void AffiliationFetchThrottler::EnsureCallbackIsScheduled() { |
+ DCHECK_EQ(state_, FETCH_NEEDED); |
mmenke
2015/01/16 21:49:54
DCHECK(has_network_connectivity_)?
engedy
2015/01/19 18:13:11
Done.
|
+ if (is_fetch_scheduled_) |
+ return; |
+ |
+ is_fetch_scheduled_ = true; |
mmenke
2015/01/16 21:28:34
Suggest using a OneShotTimer instead, it keeps tra
engedy
2015/01/19 18:13:11
I wanted to use that, but it does not allow time t
mmenke
2015/01/21 17:18:52
Ah, I wasn't aware we had a task runner class with
|
+ task_runner_->PostDelayedTask( |
+ FROM_HERE, |
+ base::Bind(&AffiliationFetchThrottler::OnBackoffDelayExpiredCallback, |
+ weak_ptr_factory_.GetWeakPtr()), |
+ exponential_backoff_->GetTimeUntilRelease()); |
mmenke
2015/01/16 21:49:54
Also, do we need async behavior here? Seems like
engedy
2015/01/19 18:13:11
While we could certainly do that, do you think put
mmenke
2015/01/21 17:18:52
ScheduleCallbackIfNeeded? (Could even have the st
engedy
2015/01/23 22:09:43
I would like to keep always async behavior. On tha
|
+} |
+ |
+void AffiliationFetchThrottler::OnBackoffDelayExpiredCallback() { |
+ DCHECK_EQ(state_, FETCH_NEEDED); |
+ DCHECK(is_fetch_scheduled_); |
+ is_fetch_scheduled_ = false; |
+ |
+ // Do nothing if network connectivity was lost while this callback was in the |
+ // task queue. The callback will be posted in the OnConnectionTypeChanged |
+ // handler once again. |
+ if (!has_network_connectivity_) |
+ return; |
+ |
+ // The release time might have been increased if network connectivity was lost |
+ // and restored while this callback was in the task queue. If so, reschedule. |
+ if (exponential_backoff_->ShouldRejectRequest()) |
+ EnsureCallbackIsScheduled(); |
+ else |
+ state_ = delegate_->OnCanSendNetworkRequest() ? FETCH_IN_FLIGHT : IDLE; |
+} |
+ |
+void AffiliationFetchThrottler::OnConnectionTypeChanged( |
+ net::NetworkChangeNotifier::ConnectionType type) { |
+ bool old_has_network_connectivity = has_network_connectivity_; |
+ has_network_connectivity_ = |
+ (type != net::NetworkChangeNotifier::CONNECTION_NONE); |
+ |
+ // Only react when network connectivity has been reestablished. |
+ if (!has_network_connectivity_ || old_has_network_connectivity) |
+ return; |
+ |
+ double grace_ms = |
+ kGracePeriodAfterNetworkRestoredInMs * |
+ (1 - base::RandDouble() * kAffiliationBackoffParameters.jitter_factor); |
+ base::TimeTicks release_time = std::max( |
+ exponential_backoff_->GetReleaseTime(), |
+ tick_clock_->NowTicks() + base::TimeDelta::FromMillisecondsD(grace_ms)); |
+ // Note that SetCustomReleaseTime() takes the maximum of the current release |
+ // time and the supplied |release_time|. |
+ exponential_backoff_->SetCustomReleaseTime(release_time); |
+ |
+ if (state_ == FETCH_NEEDED) |
+ EnsureCallbackIsScheduled(); |
+} |
+ |
+} // namespace password_manager |