forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix and refactor HttpServerPropertiesImpl's alternative services brok…
…enness expiration behavior. One of HttpServerPropertiesImpl's responsibilities is to store HTTP alternative services that have failed and keeps track of their time-until-retry (i.e. when their brokenness expires). The broken alt-svcs and their respective expiration times are kept in a list ordered by expiration time. When the expiration task runs, it drops items from the front of the list until the expiration time of the head alt-svc is no longer in the past. Turns out this list was kept in insertion order as opposed to expiration-sorted order. This change keeps this queue in expiration-sorted order, as expected. The additional logic added to implement the above change led to a refactor of HttpServerPropertiesImpl: its broken alt-svc logic is put in its own class, BrokenAlternativeServices. BUG=724302 Review-Url: https://codereview.chromium.org/2898983006 Cr-Commit-Position: refs/heads/master@{#476475}
- Loading branch information
wangyix
authored and
Commit Bot
committed
Jun 1, 2017
1 parent
3f7a5c7
commit 64ccc57
Showing
7 changed files
with
849 additions
and
149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
// Copyright (c) 2017 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/http/broken_alternative_services.h" | ||
|
||
#include "base/memory/singleton.h" | ||
#include "base/time/tick_clock.h" | ||
#include "base/time/time.h" | ||
#include "net/http/http_server_properties_impl.h" | ||
|
||
namespace net { | ||
|
||
namespace { | ||
|
||
// Initial delay for broken alternative services. | ||
const uint64_t kBrokenAlternativeProtocolDelaySecs = 300; | ||
// Subsequent failures result in exponential (base 2) backoff. | ||
// Limit binary shift to limit delay to approximately 2 days. | ||
const int kBrokenDelayMaxShift = 9; | ||
|
||
base::TimeDelta ComputeBrokenAlternativeServiceExpirationDelay( | ||
int broken_count) { | ||
DCHECK_GE(broken_count, 0); | ||
if (broken_count > kBrokenDelayMaxShift) | ||
broken_count = kBrokenDelayMaxShift; | ||
return base::TimeDelta::FromSeconds(kBrokenAlternativeProtocolDelaySecs) * | ||
(1 << broken_count); | ||
} | ||
|
||
} // namespace | ||
|
||
BrokenAlternativeServices::BrokenAlternativeServices(Delegate* delegate, | ||
base::TickClock* clock) | ||
: delegate_(delegate), | ||
clock_(clock), | ||
recently_broken_alternative_services_( | ||
RecentlyBrokenAlternativeServices::NO_AUTO_EVICT), | ||
weak_ptr_factory_(this) { | ||
DCHECK(delegate_); | ||
DCHECK(clock_); | ||
} | ||
|
||
BrokenAlternativeServices::~BrokenAlternativeServices() {} | ||
|
||
void BrokenAlternativeServices::MarkAlternativeServiceBroken( | ||
const AlternativeService& alternative_service) { | ||
// Empty host means use host of origin, callers are supposed to substitute. | ||
DCHECK(!alternative_service.host.empty()); | ||
DCHECK_NE(kProtoUnknown, alternative_service.protocol); | ||
|
||
auto it = recently_broken_alternative_services_.Get(alternative_service); | ||
int broken_count = 0; | ||
if (it == recently_broken_alternative_services_.end()) { | ||
recently_broken_alternative_services_.Put(alternative_service, 1); | ||
} else { | ||
broken_count = it->second++; | ||
} | ||
base::TimeTicks expiration = | ||
clock_->NowTicks() + | ||
ComputeBrokenAlternativeServiceExpirationDelay(broken_count); | ||
// Return if alternative service is already in expiration queue. | ||
BrokenAlternativeServiceList::iterator list_it; | ||
if (!AddToBrokenAlternativeServiceListAndMap(alternative_service, expiration, | ||
&list_it)) { | ||
return; | ||
} | ||
|
||
// If this is now the first entry in the list (i.e. |alternative_service| is | ||
// the next alt svc to expire), schedule an expiration task for it. | ||
if (list_it == broken_alternative_service_list_.begin()) { | ||
ScheduleBrokenAlternateProtocolMappingsExpiration(); | ||
} | ||
} | ||
|
||
void BrokenAlternativeServices::MarkAlternativeServiceRecentlyBroken( | ||
const AlternativeService& alternative_service) { | ||
DCHECK_NE(kProtoUnknown, alternative_service.protocol); | ||
if (recently_broken_alternative_services_.Get(alternative_service) == | ||
recently_broken_alternative_services_.end()) { | ||
recently_broken_alternative_services_.Put(alternative_service, 1); | ||
} | ||
} | ||
|
||
bool BrokenAlternativeServices::IsAlternativeServiceBroken( | ||
const AlternativeService& alternative_service) const { | ||
// Empty host means use host of origin, callers are supposed to substitute. | ||
DCHECK(!alternative_service.host.empty()); | ||
return broken_alternative_service_map_.find(alternative_service) != | ||
broken_alternative_service_map_.end(); | ||
} | ||
|
||
bool BrokenAlternativeServices::WasAlternativeServiceRecentlyBroken( | ||
const AlternativeService& alternative_service) { | ||
return recently_broken_alternative_services_.Get(alternative_service) != | ||
recently_broken_alternative_services_.end(); | ||
} | ||
|
||
void BrokenAlternativeServices::ConfirmAlternativeService( | ||
const AlternativeService& alternative_service) { | ||
DCHECK_NE(kProtoUnknown, alternative_service.protocol); | ||
|
||
// Remove |alternative_service| from |alternative_service_list_| and | ||
// |alternative_service_map_|. | ||
auto map_it = broken_alternative_service_map_.find(alternative_service); | ||
if (map_it != broken_alternative_service_map_.end()) { | ||
broken_alternative_service_list_.erase(map_it->second); | ||
broken_alternative_service_map_.erase(map_it); | ||
} | ||
|
||
auto it = recently_broken_alternative_services_.Get(alternative_service); | ||
if (it != recently_broken_alternative_services_.end()) { | ||
recently_broken_alternative_services_.Erase(it); | ||
} | ||
} | ||
|
||
bool BrokenAlternativeServices::AddToBrokenAlternativeServiceListAndMap( | ||
const AlternativeService& alternative_service, | ||
base::TimeTicks expiration, | ||
BrokenAlternativeServiceList::iterator* it) { | ||
DCHECK(it); | ||
|
||
auto map_it = broken_alternative_service_map_.find(alternative_service); | ||
if (map_it != broken_alternative_service_map_.end()) | ||
return false; | ||
|
||
// Iterate from end of |broken_alternative_service_list_| to find where to | ||
// insert it to keep the list sorted by expiration time. | ||
auto list_it = broken_alternative_service_list_.end(); | ||
while (list_it != broken_alternative_service_list_.begin()) { | ||
--list_it; | ||
if (list_it->expiration <= expiration) { | ||
++list_it; | ||
break; | ||
} | ||
} | ||
|
||
// Insert |alternative_service| into the list and the map | ||
list_it = broken_alternative_service_list_.insert( | ||
list_it, BrokenAltSvcExpireInfo(alternative_service, expiration)); | ||
broken_alternative_service_map_.insert( | ||
std::make_pair(alternative_service, list_it)); | ||
|
||
*it = list_it; | ||
return true; | ||
} | ||
|
||
void BrokenAlternativeServices::ExpireBrokenAlternateProtocolMappings() { | ||
base::TimeTicks now = clock_->NowTicks(); | ||
|
||
while (!broken_alternative_service_list_.empty()) { | ||
auto it = broken_alternative_service_list_.begin(); | ||
if (now < it->expiration) { | ||
break; | ||
} | ||
|
||
delegate_->OnExpireBrokenAlternativeService(it->alternative_service); | ||
|
||
broken_alternative_service_map_.erase(it->alternative_service); | ||
broken_alternative_service_list_.erase(it); | ||
} | ||
|
||
if (!broken_alternative_service_list_.empty()) | ||
ScheduleBrokenAlternateProtocolMappingsExpiration(); | ||
} | ||
|
||
void BrokenAlternativeServices :: | ||
ScheduleBrokenAlternateProtocolMappingsExpiration() { | ||
DCHECK(!broken_alternative_service_list_.empty()); | ||
base::TimeTicks now = clock_->NowTicks(); | ||
base::TimeTicks when = broken_alternative_service_list_.front().expiration; | ||
base::TimeDelta delay = when > now ? when - now : base::TimeDelta(); | ||
expiration_timer_.Stop(); | ||
expiration_timer_.Start( | ||
FROM_HERE, delay, | ||
base::Bind( | ||
&BrokenAlternativeServices ::ExpireBrokenAlternateProtocolMappings, | ||
weak_ptr_factory_.GetWeakPtr())); | ||
} | ||
|
||
} // namespace net |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// Copyright (c) 2017 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_HTTP_BROKEN_ALTERNATIVE_SERVICES_H_ | ||
#define NET_HTTP_BROKEN_ALTERNATIVE_SERVICES_H_ | ||
|
||
#include <list> | ||
#include <unordered_map> | ||
|
||
#include "base/memory/weak_ptr.h" | ||
#include "base/timer/timer.h" | ||
#include "net/http/http_server_properties.h" | ||
|
||
namespace base { | ||
class TickClock; | ||
} | ||
|
||
namespace net { | ||
|
||
struct AlternativeServiceHash { | ||
size_t operator()(const net::AlternativeService& entry) const { | ||
return entry.protocol ^ std::hash<std::string>()(entry.host) ^ entry.port; | ||
} | ||
}; | ||
|
||
// This class tracks HTTP alternative services that have been marked as broken. | ||
// The brokenness of an alt-svc will expire after some time according to an | ||
// exponential back-off formula: each time an alt-svc is marked broken, the | ||
// expiration delay will be some constant multiple of its previous expiration | ||
// delay. This prevents broken alt-svcs from being retried too often by the | ||
// network stack. | ||
class NET_EXPORT_PRIVATE BrokenAlternativeServices { | ||
public: | ||
// Delegate to be used by owner so it can be notified when the brokenness of | ||
// an AlternativeService expires. | ||
class NET_EXPORT Delegate { | ||
public: | ||
// Called when a broken alternative service's expiration time is reached. | ||
virtual void OnExpireBrokenAlternativeService( | ||
const AlternativeService& expired_alternative_service) = 0; | ||
virtual ~Delegate() {} | ||
}; | ||
|
||
// |delegate| will be notified when a broken alternative service expires. It | ||
// must not be null. | ||
// |clock| is used for setting expiration times and scheduling the | ||
// expiration of broken alternative services. It must not be null. | ||
// |delegate| and |clock| are both unowned and must outlive this. | ||
BrokenAlternativeServices(Delegate* delegate, base::TickClock* clock); | ||
|
||
BrokenAlternativeServices(const BrokenAlternativeServices&) = delete; | ||
void operator=(const BrokenAlternativeServices&) = delete; | ||
|
||
~BrokenAlternativeServices(); | ||
|
||
// Marks |alternative_service| as broken until after some expiration delay | ||
// (determined by how many times it's been marked broken before). Being broken | ||
// will cause IsAlternativeServiceBroken(alternative_service) to return true | ||
// until the expiration time is reached, or until | ||
// ConfirmAlternativeService(alternative_service) is called. | ||
void MarkAlternativeServiceBroken( | ||
const AlternativeService& alternative_service); | ||
|
||
// Marks |alternative_service| as recently broken. Being recently broken will | ||
// cause WasAlternativeServiceRecentlyBroken(alternative_service) to return | ||
// true until ConfirmAlternativeService(alternative_service) is called. | ||
void MarkAlternativeServiceRecentlyBroken( | ||
const AlternativeService& alternative_service); | ||
|
||
// Returns true if MarkAlternativeServiceBroken(alternative_service) has been | ||
// called, the expiration time has not been reached, and | ||
// ConfirmAlternativeService(alternative_service) has not been called | ||
// afterwards. | ||
bool IsAlternativeServiceBroken( | ||
const AlternativeService& alternative_service) const; | ||
|
||
// Returns true if MarkAlternativeServiceRecentlyBroken(alternative_service) | ||
// or MarkAlternativeServiceBroken(alternative_service) has been called and | ||
// ConfirmAlternativeService(alternative_service) has not been called | ||
// afterwards (even if brokenness of |alternative_service| has expired). | ||
bool WasAlternativeServiceRecentlyBroken( | ||
const AlternativeService& alternative_service); | ||
|
||
// Marks |alternative_service| as not broken and not recently broken. | ||
void ConfirmAlternativeService(const AlternativeService& alternative_service); | ||
|
||
private: | ||
// TODO (wangyix): modify HttpServerPropertiesImpl unit tests so this | ||
// friendness is no longer required. | ||
friend class HttpServerPropertiesImplPeer; | ||
|
||
// A pair containing a broken AlternativeService and the expiration time of | ||
// its brokenness. | ||
struct BrokenAltSvcExpireInfo { | ||
BrokenAltSvcExpireInfo(const AlternativeService& alt_svc, | ||
base::TimeTicks expire) | ||
: alternative_service(alt_svc), expiration(expire) {} | ||
|
||
AlternativeService alternative_service; | ||
base::TimeTicks expiration; | ||
}; | ||
|
||
typedef std::list<BrokenAltSvcExpireInfo> BrokenAlternativeServiceList; | ||
|
||
typedef std::unordered_map<AlternativeService, | ||
BrokenAlternativeServiceList::iterator, | ||
AlternativeServiceHash> | ||
BrokenAlternativeServiceMap; | ||
|
||
// Inserts |alternative_service| and its |expiration| time into | ||
// |broken_alternative_service_list_| and |broken_alternative_service_map_|. | ||
// |it| is the position in |broken_alternative_service_list_| where it was | ||
// inserted. | ||
bool AddToBrokenAlternativeServiceListAndMap( | ||
const AlternativeService& alternative_service, | ||
base::TimeTicks expiration, | ||
BrokenAlternativeServiceList::iterator* it); | ||
|
||
void ExpireBrokenAlternateProtocolMappings(); | ||
void ScheduleBrokenAlternateProtocolMappingsExpiration(); | ||
|
||
Delegate* delegate_; // Unowned | ||
base::TickClock* clock_; // Unowned | ||
|
||
// List of <broken alt svc, expiration time> pairs sorted by expiration time. | ||
BrokenAlternativeServiceList broken_alternative_service_list_; | ||
// A map from broken alt-svcs to their iterator pointing to that alt-svc's | ||
// position in |broken_alternative_service_list_|. | ||
BrokenAlternativeServiceMap broken_alternative_service_map_; | ||
|
||
// Maps broken alternative services to how many times they've been marked | ||
// broken. | ||
RecentlyBrokenAlternativeServices recently_broken_alternative_services_; | ||
|
||
// Used for scheduling the task that expires the brokenness of alternative | ||
// services. | ||
base::OneShotTimer expiration_timer_; | ||
|
||
base::WeakPtrFactory<BrokenAlternativeServices> weak_ptr_factory_; | ||
}; | ||
|
||
} // namespace net | ||
|
||
#endif // NET_HTTP_BROKEN_ALTERNATIVE_SERVICES_H_ |
Oops, something went wrong.