Commit 89726c25 authored by engedy's avatar engedy Committed by Commit bot

Implement throttling logic for fetching affiliation information.

BUG=437865

Review URL: https://codereview.chromium.org/807503002

Cr-Commit-Position: refs/heads/master@{#314127}
parent 71f5a07c
...@@ -102,6 +102,11 @@ bool TestMockTimeTaskRunner::HasPendingTask() const { ...@@ -102,6 +102,11 @@ bool TestMockTimeTaskRunner::HasPendingTask() const {
return !tasks_.empty(); return !tasks_.empty();
} }
size_t TestMockTimeTaskRunner::GetPendingTaskCount() const {
DCHECK(thread_checker_.CalledOnValidThread());
return tasks_.size();
}
TimeDelta TestMockTimeTaskRunner::NextPendingTaskDelay() const { TimeDelta TestMockTimeTaskRunner::NextPendingTaskDelay() const {
DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(thread_checker_.CalledOnValidThread());
return tasks_.empty() ? TimeDelta::Max() : tasks_.top().GetTimeToRun() - now_; return tasks_.empty() ? TimeDelta::Max() : tasks_.top().GetTimeToRun() - now_;
......
...@@ -60,6 +60,7 @@ class TestMockTimeTaskRunner : public base::SingleThreadTaskRunner { ...@@ -60,6 +60,7 @@ class TestMockTimeTaskRunner : public base::SingleThreadTaskRunner {
scoped_ptr<TickClock> GetMockTickClock() const; scoped_ptr<TickClock> GetMockTickClock() const;
bool HasPendingTask() const; bool HasPendingTask() const;
size_t GetPendingTaskCount() const;
TimeDelta NextPendingTaskDelay() const; TimeDelta NextPendingTaskDelay() const;
// SingleThreadTaskRunner: // SingleThreadTaskRunner:
......
...@@ -192,6 +192,7 @@ ...@@ -192,6 +192,7 @@
'packed_ct_ev_whitelist/bit_stream_reader_unittest.cc', 'packed_ct_ev_whitelist/bit_stream_reader_unittest.cc',
'packed_ct_ev_whitelist/packed_ct_ev_whitelist_unittest.cc', 'packed_ct_ev_whitelist/packed_ct_ev_whitelist_unittest.cc',
'password_manager/core/browser/affiliation_database_unittest.cc', 'password_manager/core/browser/affiliation_database_unittest.cc',
'password_manager/core/browser/affiliation_fetch_throttler_unittest.cc',
'password_manager/core/browser/affiliation_fetcher_unittest.cc', 'password_manager/core/browser/affiliation_fetcher_unittest.cc',
'password_manager/core/browser/affiliation_utils_unittest.cc', 'password_manager/core/browser/affiliation_utils_unittest.cc',
'password_manager/core/browser/browser_save_password_progress_logger_unittest.cc', 'password_manager/core/browser/browser_save_password_progress_logger_unittest.cc',
......
...@@ -32,6 +32,9 @@ ...@@ -32,6 +32,9 @@
'password_manager/core/browser/affiliation_fetcher_delegate.h', 'password_manager/core/browser/affiliation_fetcher_delegate.h',
'password_manager/core/browser/affiliation_database.cc', 'password_manager/core/browser/affiliation_database.cc',
'password_manager/core/browser/affiliation_database.h', 'password_manager/core/browser/affiliation_database.h',
'password_manager/core/browser/affiliation_fetch_throttler.cc',
'password_manager/core/browser/affiliation_fetch_throttler.h',
'password_manager/core/browser/affiliation_fetch_throttler_delegate.h',
'password_manager/core/browser/affiliation_fetcher.cc', 'password_manager/core/browser/affiliation_fetcher.cc',
'password_manager/core/browser/affiliation_fetcher.h', 'password_manager/core/browser/affiliation_fetcher.h',
'password_manager/core/browser/affiliation_service.cc', 'password_manager/core/browser/affiliation_service.cc',
......
...@@ -21,6 +21,9 @@ static_library("browser") { ...@@ -21,6 +21,9 @@ static_library("browser") {
"affiliation_backend.h", "affiliation_backend.h",
"affiliation_database.cc", "affiliation_database.cc",
"affiliation_database.h", "affiliation_database.h",
"affiliation_fetch_throttler.cc",
"affiliation_fetch_throttler.h",
"affiliation_fetch_throttler_delegate.h",
"affiliation_fetcher_delegate.h", "affiliation_fetcher_delegate.h",
"affiliation_fetcher.cc", "affiliation_fetcher.cc",
"affiliation_fetcher.h", "affiliation_fetcher.h",
......
// 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"
namespace password_manager {
namespace {
// Implementation of net::BackoffEntry that allows mocking its tick source.
class BackoffEntryImpl : public net::BackoffEntry {
public:
// |tick_clock| must outlive this instance.
explicit BackoffEntryImpl(const net::BackoffEntry::Policy* const policy,
base::TickClock* tick_clock)
: BackoffEntry(policy), tick_clock_(tick_clock) {}
~BackoffEntryImpl() override {}
private:
// net::BackoffEntry:
base::TimeTicks ImplGetTimeNow() const override {
return tick_clock_->NowTicks();
}
base::TickClock* tick_clock_;
DISALLOW_COPY_AND_ASSIGN(BackoffEntryImpl);
};
} // namespace
// static
const net::BackoffEntry::Policy AffiliationFetchThrottler::kBackoffPolicy = {
// 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,
};
// static
const int64_t AffiliationFetchThrottler::kGracePeriodAfterReconnectMs =
10 * 1000; // 10 seconds
AffiliationFetchThrottler::AffiliationFetchThrottler(
AffiliationFetchThrottlerDelegate* delegate,
const 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(&kBackoffPolicy, tick_clock_.get())),
weak_ptr_factory_(this) {
DCHECK(delegate);
// Start observing before querying the current connectivity state, so that if
// the state changes concurrently in-between, it will not go unnoticed.
net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
has_network_connectivity_ = !net::NetworkChangeNotifier::IsOffline();
}
AffiliationFetchThrottler::~AffiliationFetchThrottler() {
net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
}
void AffiliationFetchThrottler::SignalNetworkRequestNeeded() {
if (state_ != IDLE)
return;
state_ = FETCH_NEEDED;
if (has_network_connectivity_)
EnsureCallbackIsScheduled();
}
void AffiliationFetchThrottler::InformOfNetworkRequestComplete(bool success) {
DCHECK_EQ(state_, FETCH_IN_FLIGHT);
state_ = IDLE;
exponential_backoff_->InformOfRequest(success);
}
void AffiliationFetchThrottler::EnsureCallbackIsScheduled() {
DCHECK_EQ(state_, FETCH_NEEDED);
DCHECK(has_network_connectivity_);
if (is_fetch_scheduled_)
return;
is_fetch_scheduled_ = true;
task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&AffiliationFetchThrottler::OnBackoffDelayExpiredCallback,
weak_ptr_factory_.GetWeakPtr()),
exponential_backoff_->GetTimeUntilRelease());
}
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 = kGracePeriodAfterReconnectMs *
(1 - base::RandDouble() * kBackoffPolicy.jitter_factor);
exponential_backoff_->SetCustomReleaseTime(std::max(
exponential_backoff_->GetReleaseTime(),
tick_clock_->NowTicks() + base::TimeDelta::FromMillisecondsD(grace_ms)));
if (state_ == FETCH_NEEDED)
EnsureCallbackIsScheduled();
}
} // namespace password_manager
// 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.
#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_AFFILIATION_FETCH_THROTTLER_H_
#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_AFFILIATION_FETCH_THROTTLER_H_
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "net/base/backoff_entry.h"
#include "net/base/network_change_notifier.h"
namespace base {
class TickClock;
class SingleThreadTaskRunner;
} // namespace base
namespace password_manager {
class AffiliationFetchThrottlerDelegate;
// Implements the throttling logic that the AffiliationBackend will use when it
// needs to issue requests over the network to fetch affiliation information.
//
// This class manages only the scheduling of the requests. It is up to the
// consumer (the AffiliationBackend) to actually assemble and send the requests,
// to report back about their success or failure, and to retry them if desired.
// The process goes like this:
// 1.) The consumer calls SignalNetworkRequestNeeded().
// 2.) Once appropriate, OnCanSendNetworkRequest() is called on the delegate.
// 3.) The consumer sends the request, and waits until it completes.
// 4.) The consumer calls InformOfNetworkRequestComplete().
// Note that only a single request at a time is supported.
//
// If the request fails in Step 3, the consumer should not automatically retry
// it. Instead it should always proceed to Step 4, and then -- if retrying the
// request is desired -- proceed immediately to Step 1. That is, it should act
// as if another request was needed right away.
//
// Essentially, this class implements exponential backoff in case of network and
// server errors with the additional constraint that no requests will be issued
// in the first place while there is known to be no network connectivity. This
// prevents the exponential backoff delay from growing huge during long offline
// periods, so that requests will not be held back for too long after
// connectivity is restored.
class AffiliationFetchThrottler
: public net::NetworkChangeNotifier::ConnectionTypeObserver {
public:
// Creates an instance that will use |tick_clock| as its tick source, and will
// post to |task_runner| to call the |delegate|'s OnSendNetworkRequest(). The
// |delegate| should outlive the throttler.
AffiliationFetchThrottler(
AffiliationFetchThrottlerDelegate* delegate,
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
scoped_ptr<base::TickClock> tick_clock);
~AffiliationFetchThrottler() override;
// Signals to the throttling logic that a network request is needed, and that
// OnCanSendNetworkRequest() should be called as soon as the request can be
// sent. OnCanSendNetworkRequest() will always be called asynchronously.
//
// Calls to this method will be ignored when a request is already known to be
// needed or while a request is in flight. To signal that another request will
// be needed right away after the current one, call this method after calling
// InformOfNetworkRequestComplete().
void SignalNetworkRequestNeeded();
// Informs the back-off logic that the in-flight network request has been
// completed, either with |success| or not.
void InformOfNetworkRequestComplete(bool success);
private:
FRIEND_TEST_ALL_PREFIXES(AffiliationFetchThrottlerTest, FailedRequests);
FRIEND_TEST_ALL_PREFIXES(AffiliationFetchThrottlerTest,
GracePeriodAfterConnectivityIsRestored);
FRIEND_TEST_ALL_PREFIXES(AffiliationFetchThrottlerTest,
GracePeriodAfterConnectivityIsRestored2);
FRIEND_TEST_ALL_PREFIXES(AffiliationFetchThrottlerTest,
GracePeriodAfterConnectivityIsRestored3);
FRIEND_TEST_ALL_PREFIXES(AffiliationFetchThrottlerTest,
ConnectivityLostDuringBackoff);
FRIEND_TEST_ALL_PREFIXES(AffiliationFetchThrottlerTest,
ConnectivityLostAndRestoredDuringBackoff);
FRIEND_TEST_ALL_PREFIXES(AffiliationFetchThrottlerTest, FlakyConnectivity);
FRIEND_TEST_ALL_PREFIXES(AffiliationFetchThrottlerTest,
ConnectivityLostDuringRequest);
FRIEND_TEST_ALL_PREFIXES(AffiliationFetchThrottlerTest,
ConnectivityLostAndRestoredDuringRequest);
FRIEND_TEST_ALL_PREFIXES(AffiliationFetchThrottlerTest,
ConnectivityLostAndRestoredDuringRequest2);
enum State { IDLE, FETCH_NEEDED, FETCH_IN_FLIGHT };
// Exponential backoff parameters in case of network and server errors
static const net::BackoffEntry::Policy kBackoffPolicy;
// Minimum delay before sending the first request once network connectivity is
// restored. The fuzzing factor in |kBackoffParameters.jitter_factor| applies.
static const int64_t kGracePeriodAfterReconnectMs;
// Ensures that OnBackoffDelayExpiredCallback() is scheduled to be called back
// once the |exponential_backoff_| delay expires.
void EnsureCallbackIsScheduled();
// Called back when the |exponential_backoff_| delay expires.
void OnBackoffDelayExpiredCallback();
// net::NetworkChangeNotifier::ConnectionTypeObserver:
void OnConnectionTypeChanged(
net::NetworkChangeNotifier::ConnectionType type) override;
AffiliationFetchThrottlerDelegate* delegate_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
State state_;
bool has_network_connectivity_;
bool is_fetch_scheduled_;
scoped_ptr<base::TickClock> tick_clock_;
scoped_ptr<net::BackoffEntry> exponential_backoff_;
base::WeakPtrFactory<AffiliationFetchThrottler> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(AffiliationFetchThrottler);
};
} // namespace password_manager
#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_AFFILIATION_FETCH_THROTTLER_H_
// 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.
#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_AFFILIATION_FETCH_THROTTLER_DELEGATE_H_
#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_AFFILIATION_FETCH_THROTTLER_DELEGATE_H_
namespace password_manager {
// An interface that users of AffiliationFetchThrottler need to implement to get
// notified once it is okay to issue the next network request.
class AffiliationFetchThrottlerDelegate {
public:
// Will be called once the throttling policy allows issuing a network request,
// provided SignalNetworkRequestNeeded() has been called at least once since
// the last request.
//
// The implementation must return true if a request was actually issued in
// response to this call, and then call InformOfNetworkRequestComplete() once
// the request is complete. Otherwise, the implementation must return false.
virtual bool OnCanSendNetworkRequest() = 0;
protected:
virtual ~AffiliationFetchThrottlerDelegate() {}
};
} // namespace password_manager
#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_AFFILIATION_FETCH_THROTTLER_DELEGATE_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment