Chromium Code Reviews| 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 |