Commit 8e55d7a3 authored by Yutaka Hirano's avatar Yutaka Hirano Committed by Commit Bot

Have WebSocket throttling work with Network Service

This CL introduces WebSocket throttling on network::WebSocketFactory by
moving some logic from content/browser/websockets to services/network.

Bug: 721400
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_mojo
Change-Id: Ie007765411d95d1854fc0271652d599e5ccbcd0c
Reviewed-on: https://chromium-review.googlesource.com/979872
Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
Reviewed-by: default avatarAdam Rice <ricea@chromium.org>
Cr-Commit-Position: refs/heads/master@{#545766}
parent 6a4762ec
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/rand_util.h"
#include "content/browser/bad_message.h" #include "content/browser/bad_message.h"
#include "content/browser/child_process_security_policy_impl.h" #include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/ssl/ssl_error_handler.h" #include "content/browser/ssl/ssl_error_handler.h"
...@@ -31,10 +30,6 @@ namespace { ...@@ -31,10 +30,6 @@ namespace {
const char kWebSocketManagerKeyName[] = "web_socket_manager"; const char kWebSocketManagerKeyName[] = "web_socket_manager";
// Max number of pending connections per WebSocketManager used for per-renderer
// WebSocket throttling.
const int kMaxPendingWebSocketConnections = 255;
} // namespace } // namespace
class WebSocketManager::Delegate final : public network::WebSocket::Delegate { class WebSocketManager::Delegate final : public network::WebSocket::Delegate {
...@@ -46,10 +41,6 @@ class WebSocketManager::Delegate final : public network::WebSocket::Delegate { ...@@ -46,10 +41,6 @@ class WebSocketManager::Delegate final : public network::WebSocket::Delegate {
return manager_->GetURLRequestContext(); return manager_->GetURLRequestContext();
} }
void OnReceivedResponseFromServer(network::WebSocket* impl) override {
manager_->OnReceivedResponseFromServer(impl);
}
void OnLostConnectionToClient(network::WebSocket* impl) override { void OnLostConnectionToClient(network::WebSocket* impl) override {
manager_->OnLostConnectionToClient(impl); manager_->OnLostConnectionToClient(impl);
} }
...@@ -216,11 +207,6 @@ void WebSocketManager::CreateWebSocketWithOrigin( ...@@ -216,11 +207,6 @@ void WebSocketManager::CreateWebSocketWithOrigin(
WebSocketManager::WebSocketManager(int process_id, WebSocketManager::WebSocketManager(int process_id,
StoragePartition* storage_partition) StoragePartition* storage_partition)
: process_id_(process_id), : process_id_(process_id),
num_pending_connections_(0),
num_current_succeeded_connections_(0),
num_previous_succeeded_connections_(0),
num_current_failed_connections_(0),
num_previous_failed_connections_(0),
context_destroyed_(false) { context_destroyed_(false) {
if (storage_partition) { if (storage_partition) {
url_request_context_getter_ = storage_partition->GetURLRequestContext(); url_request_context_getter_ = storage_partition->GetURLRequestContext();
...@@ -251,7 +237,7 @@ void WebSocketManager::DoCreateWebSocket( ...@@ -251,7 +237,7 @@ void WebSocketManager::DoCreateWebSocket(
network::mojom::WebSocketRequest request) { network::mojom::WebSocketRequest request) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (num_pending_connections_ >= kMaxPendingWebSocketConnections) { if (throttler_.HasTooManyPendingConnections()) {
// Too many websockets! // Too many websockets!
request.ResetWithReason( request.ResetWithReason(
network::mojom::WebSocket::kInsufficientResources, network::mojom::WebSocket::kInsufficientResources,
...@@ -269,10 +255,10 @@ void WebSocketManager::DoCreateWebSocket( ...@@ -269,10 +255,10 @@ void WebSocketManager::DoCreateWebSocket(
// Keep all network::WebSockets alive until either the client drops its // Keep all network::WebSockets alive until either the client drops its
// connection (see OnLostConnectionToClient) or we need to shutdown. // connection (see OnLostConnectionToClient) or we need to shutdown.
impls_.insert(CreateWebSocket(std::make_unique<Delegate>(this), impls_.insert(CreateWebSocket(
std::move(request), process_id_, frame_id, std::make_unique<Delegate>(this), std::move(request),
std::move(origin), CalculateDelay())); throttler_.IssuePendingConnectionTracker(), process_id_, frame_id,
++num_pending_connections_; std::move(origin), throttler_.CalculateDelay()));
if (!throttling_period_timer_.IsRunning()) { if (!throttling_period_timer_.IsRunning()) {
throttling_period_timer_.Start( throttling_period_timer_.Start(
...@@ -283,42 +269,23 @@ void WebSocketManager::DoCreateWebSocket( ...@@ -283,42 +269,23 @@ void WebSocketManager::DoCreateWebSocket(
} }
} }
// Calculate delay as described in the per-renderer WebSocket throttling
// design doc: https://goo.gl/tldFNn
base::TimeDelta WebSocketManager::CalculateDelay() const {
int64_t f = num_previous_failed_connections_ +
num_current_failed_connections_;
int64_t s = num_previous_succeeded_connections_ +
num_current_succeeded_connections_;
int p = num_pending_connections_;
return base::TimeDelta::FromMilliseconds(
base::RandInt(1000, 5000) *
(1 << std::min(p + f / (s + 1), INT64_C(16))) / 65536);
}
void WebSocketManager::ThrottlingPeriodTimerCallback() { void WebSocketManager::ThrottlingPeriodTimerCallback() {
num_previous_failed_connections_ = num_current_failed_connections_; throttler_.Roll();
num_current_failed_connections_ = 0; if (throttler_.IsClean())
num_previous_succeeded_connections_ = num_current_succeeded_connections_;
num_current_succeeded_connections_ = 0;
if (num_pending_connections_ == 0 &&
num_previous_failed_connections_ == 0 &&
num_previous_succeeded_connections_ == 0) {
throttling_period_timer_.Stop(); throttling_period_timer_.Stop();
}
} }
std::unique_ptr<network::WebSocket> WebSocketManager::CreateWebSocket( std::unique_ptr<network::WebSocket> WebSocketManager::CreateWebSocket(
std::unique_ptr<network::WebSocket::Delegate> delegate, std::unique_ptr<network::WebSocket::Delegate> delegate,
network::mojom::WebSocketRequest request, network::mojom::WebSocketRequest request,
network::WebSocketThrottler::PendingConnection pending_connection_tracker,
int child_id, int child_id,
int frame_id, int frame_id,
url::Origin origin, url::Origin origin,
base::TimeDelta delay) { base::TimeDelta delay) {
return std::make_unique<network::WebSocket>( return std::make_unique<network::WebSocket>(
std::move(delegate), std::move(request), child_id, frame_id, std::move(delegate), std::move(request),
std::move(pending_connection_tracker), child_id, frame_id,
std::move(origin), delay); std::move(origin), delay);
} }
...@@ -326,22 +293,8 @@ net::URLRequestContext* WebSocketManager::GetURLRequestContext() { ...@@ -326,22 +293,8 @@ net::URLRequestContext* WebSocketManager::GetURLRequestContext() {
return url_request_context_getter_->GetURLRequestContext(); return url_request_context_getter_->GetURLRequestContext();
} }
void WebSocketManager::OnReceivedResponseFromServer(network::WebSocket* impl) {
// The server accepted this WebSocket connection.
impl->OnHandshakeSucceeded();
--num_pending_connections_;
DCHECK_GE(num_pending_connections_, 0);
++num_current_succeeded_connections_;
}
void WebSocketManager::OnLostConnectionToClient(network::WebSocket* impl) { void WebSocketManager::OnLostConnectionToClient(network::WebSocket* impl) {
// The client is no longer interested in this WebSocket. // The client is no longer interested in this WebSocket.
if (!impl->handshake_succeeded()) {
// Update throttling counters (failure).
--num_pending_connections_;
DCHECK_GE(num_pending_connections_, 0);
++num_current_failed_connections_;
}
impl->GoAway(); impl->GoAway();
const auto it = impls_.find(impl); const auto it = impls_.find(impl);
DCHECK(it != impls_.end()); DCHECK(it != impls_.end());
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_context_getter_observer.h" #include "net/url_request/url_request_context_getter_observer.h"
#include "services/network/websocket.h" #include "services/network/websocket.h"
#include "services/network/websocket_throttler.h"
namespace content { namespace content {
class StoragePartition; class StoragePartition;
...@@ -58,20 +59,19 @@ class CONTENT_EXPORT WebSocketManager ...@@ -58,20 +59,19 @@ class CONTENT_EXPORT WebSocketManager
void DoCreateWebSocket(int frame_id, void DoCreateWebSocket(int frame_id,
url::Origin origin, url::Origin origin,
network::mojom::WebSocketRequest request); network::mojom::WebSocketRequest request);
base::TimeDelta CalculateDelay() const;
void ThrottlingPeriodTimerCallback(); void ThrottlingPeriodTimerCallback();
// This is virtual to support testing. // This is virtual to support testing.
virtual std::unique_ptr<network::WebSocket> CreateWebSocket( virtual std::unique_ptr<network::WebSocket> CreateWebSocket(
std::unique_ptr<network::WebSocket::Delegate> delegate, std::unique_ptr<network::WebSocket::Delegate> delegate,
network::mojom::WebSocketRequest request, network::mojom::WebSocketRequest request,
network::WebSocketThrottler::PendingConnection pending_connection_tracker,
int child_id, int child_id,
int frame_id, int frame_id,
url::Origin origin, url::Origin origin,
base::TimeDelta delay); base::TimeDelta delay);
net::URLRequestContext* GetURLRequestContext(); net::URLRequestContext* GetURLRequestContext();
void OnReceivedResponseFromServer(network::WebSocket* impl);
virtual void OnLostConnectionToClient(network::WebSocket* impl); virtual void OnLostConnectionToClient(network::WebSocket* impl);
void ObserveURLRequestContextGetter(); void ObserveURLRequestContextGetter();
...@@ -85,18 +85,7 @@ class CONTENT_EXPORT WebSocketManager ...@@ -85,18 +85,7 @@ class CONTENT_EXPORT WebSocketManager
// Timer and counters for per-renderer WebSocket throttling. // Timer and counters for per-renderer WebSocket throttling.
base::RepeatingTimer throttling_period_timer_; base::RepeatingTimer throttling_period_timer_;
// The current number of pending connections. network::WebSocketPerProcessThrottler throttler_;
int num_pending_connections_;
// The number of handshakes that failed in the current and previous time
// period.
int64_t num_current_succeeded_connections_;
int64_t num_previous_succeeded_connections_;
// The number of handshakes that succeeded in the current and previous time
// period.
int64_t num_current_failed_connections_;
int64_t num_previous_failed_connections_;
bool context_destroyed_; bool context_destroyed_;
......
...@@ -22,14 +22,18 @@ static const int kMagicRenderProcessId = 506116062; ...@@ -22,14 +22,18 @@ static const int kMagicRenderProcessId = 506116062;
class TestWebSocketImpl : public network::WebSocket { class TestWebSocketImpl : public network::WebSocket {
public: public:
TestWebSocketImpl(std::unique_ptr<Delegate> delegate, TestWebSocketImpl(
network::mojom::WebSocketRequest request, std::unique_ptr<Delegate> delegate,
int process_id, network::mojom::WebSocketRequest request,
int frame_id, network::WebSocketThrottler::PendingConnection pending_connection_tracker,
url::Origin origin,
base::TimeDelta delay) int process_id,
int frame_id,
url::Origin origin,
base::TimeDelta delay)
: network::WebSocket(std::move(delegate), : network::WebSocket(std::move(delegate),
std::move(request), std::move(request),
std::move(pending_connection_tracker),
process_id, process_id,
frame_id, frame_id,
std::move(origin), std::move(origin),
...@@ -51,15 +55,20 @@ class TestWebSocketManager : public WebSocketManager { ...@@ -51,15 +55,20 @@ class TestWebSocketManager : public WebSocketManager {
return sockets_; return sockets_;
} }
int num_pending_connections() const { int64_t num_pending_connections() const {
return num_pending_connections_; return throttler_.num_pending_connections();
} }
int64_t num_failed_connections() const { int64_t num_current_succeeded_connections() const {
return num_current_failed_connections_ + num_previous_failed_connections_; return throttler_.num_current_succeeded_connections();
} }
int64_t num_succeeded_connections() const { int64_t num_previous_succeeded_connections() const {
return num_current_succeeded_connections_ + return throttler_.num_previous_succeeded_connections();
num_previous_succeeded_connections_; }
int64_t num_current_failed_connections() const {
return throttler_.num_current_failed_connections();
}
int64_t num_previous_failed_connections() const {
return throttler_.num_previous_failed_connections();
} }
void DoCreateWebSocket(network::mojom::WebSocketRequest request) { void DoCreateWebSocket(network::mojom::WebSocketRequest request) {
...@@ -71,12 +80,14 @@ class TestWebSocketManager : public WebSocketManager { ...@@ -71,12 +80,14 @@ class TestWebSocketManager : public WebSocketManager {
std::unique_ptr<network::WebSocket> CreateWebSocket( std::unique_ptr<network::WebSocket> CreateWebSocket(
std::unique_ptr<network::WebSocket::Delegate> delegate, std::unique_ptr<network::WebSocket::Delegate> delegate,
network::mojom::WebSocketRequest request, network::mojom::WebSocketRequest request,
network::WebSocketThrottler::PendingConnection pending_connection_tracker,
int process_id, int process_id,
int frame_id, int frame_id,
url::Origin origin, url::Origin origin,
base::TimeDelta delay) override { base::TimeDelta delay) override {
auto impl = std::make_unique<TestWebSocketImpl>( auto impl = std::make_unique<TestWebSocketImpl>(
std::move(delegate), std::move(request), process_id, frame_id, std::move(delegate), std::move(request),
std::move(pending_connection_tracker), process_id, frame_id,
std::move(origin), delay); std::move(origin), delay);
// We keep a vector of sockets here to track their creation order. // We keep a vector of sockets here to track their creation order.
sockets_.push_back(impl.get()); sockets_.push_back(impl.get());
...@@ -146,109 +157,19 @@ TEST_F(WebSocketManagerTest, SendFrameButNotConnectedYet) { ...@@ -146,109 +157,19 @@ TEST_F(WebSocketManagerTest, SendFrameButNotConnectedYet) {
websocket->SendFrame(true, network::mojom::WebSocketMessageType::TEXT, data); websocket->SendFrame(true, network::mojom::WebSocketMessageType::TEXT, data);
} }
TEST_F(WebSocketManagerTest, DelayFor4thPendingConnectionIsZero) {
AddMultipleChannels(4);
EXPECT_EQ(4, websocket_manager()->num_pending_connections());
EXPECT_EQ(0, websocket_manager()->num_failed_connections());
EXPECT_EQ(0, websocket_manager()->num_succeeded_connections());
ASSERT_EQ(4U, websocket_manager()->sockets().size());
EXPECT_EQ(base::TimeDelta(), websocket_manager()->sockets()[3]->delay());
}
TEST_F(WebSocketManagerTest, DelayFor8thPendingConnectionIsNonZero) {
AddMultipleChannels(8);
EXPECT_EQ(8, websocket_manager()->num_pending_connections());
EXPECT_EQ(0, websocket_manager()->num_failed_connections());
EXPECT_EQ(0, websocket_manager()->num_succeeded_connections());
ASSERT_EQ(8U, websocket_manager()->sockets().size());
EXPECT_LT(base::TimeDelta(), websocket_manager()->sockets()[7]->delay());
}
TEST_F(WebSocketManagerTest, DelayFor17thPendingConnection) {
AddMultipleChannels(17);
EXPECT_EQ(17, websocket_manager()->num_pending_connections());
EXPECT_EQ(0, websocket_manager()->num_failed_connections());
EXPECT_EQ(0, websocket_manager()->num_succeeded_connections());
ASSERT_EQ(17U, websocket_manager()->sockets().size());
EXPECT_LE(base::TimeDelta::FromMilliseconds(1000),
websocket_manager()->sockets()[16]->delay());
EXPECT_GE(base::TimeDelta::FromMilliseconds(5000),
websocket_manager()->sockets()[16]->delay());
}
// The 256th connection is rejected by per-renderer WebSocket throttling. // The 256th connection is rejected by per-renderer WebSocket throttling.
// This is not counted as a failure. // This is not counted as a failure.
TEST_F(WebSocketManagerTest, Rejects256thPendingConnection) { TEST_F(WebSocketManagerTest, Rejects256thPendingConnection) {
AddMultipleChannels(256); AddMultipleChannels(256);
EXPECT_EQ(255, websocket_manager()->num_pending_connections()); EXPECT_EQ(255, websocket_manager()->num_pending_connections());
EXPECT_EQ(0, websocket_manager()->num_failed_connections()); EXPECT_EQ(0, websocket_manager()->num_current_succeeded_connections());
EXPECT_EQ(0, websocket_manager()->num_succeeded_connections()); EXPECT_EQ(0, websocket_manager()->num_previous_succeeded_connections());
EXPECT_EQ(0, websocket_manager()->num_current_failed_connections());
EXPECT_EQ(0, websocket_manager()->num_previous_failed_connections());
ASSERT_EQ(255U, websocket_manager()->sockets().size()); ASSERT_EQ(255U, websocket_manager()->sockets().size());
} }
TEST_F(WebSocketManagerTest, DelayIsZeroAfter3FailedConnections) {
AddAndCancelMultipleChannels(3);
EXPECT_EQ(0, websocket_manager()->num_pending_connections());
EXPECT_EQ(3, websocket_manager()->num_failed_connections());
EXPECT_EQ(0, websocket_manager()->num_succeeded_connections());
AddMultipleChannels(1);
ASSERT_EQ(1U, websocket_manager()->sockets().size());
EXPECT_EQ(base::TimeDelta(), websocket_manager()->sockets()[0]->delay());
}
TEST_F(WebSocketManagerTest, DelayIsNonZeroAfter7FailedConnections) {
AddAndCancelMultipleChannels(7);
EXPECT_EQ(0, websocket_manager()->num_pending_connections());
EXPECT_EQ(7, websocket_manager()->num_failed_connections());
EXPECT_EQ(0, websocket_manager()->num_succeeded_connections());
AddMultipleChannels(1);
ASSERT_EQ(1U, websocket_manager()->sockets().size());
EXPECT_LT(base::TimeDelta(), websocket_manager()->sockets()[0]->delay());
}
TEST_F(WebSocketManagerTest, DelayAfter16FailedConnections) {
AddAndCancelMultipleChannels(16);
EXPECT_EQ(0, websocket_manager()->num_pending_connections());
EXPECT_EQ(16, websocket_manager()->num_failed_connections());
EXPECT_EQ(0, websocket_manager()->num_succeeded_connections());
AddMultipleChannels(1);
ASSERT_EQ(1U, websocket_manager()->sockets().size());
EXPECT_LE(base::TimeDelta::FromMilliseconds(1000),
websocket_manager()->sockets()[0]->delay());
EXPECT_GE(base::TimeDelta::FromMilliseconds(5000),
websocket_manager()->sockets()[0]->delay());
}
TEST_F(WebSocketManagerTest, NotRejectedAfter255FailedConnections) {
AddAndCancelMultipleChannels(255);
EXPECT_EQ(0, websocket_manager()->num_pending_connections());
EXPECT_EQ(255, websocket_manager()->num_failed_connections());
EXPECT_EQ(0, websocket_manager()->num_succeeded_connections());
AddMultipleChannels(1);
EXPECT_EQ(1, websocket_manager()->num_pending_connections());
EXPECT_EQ(255, websocket_manager()->num_failed_connections());
EXPECT_EQ(0, websocket_manager()->num_succeeded_connections());
}
} // namespace } // namespace
} // namespace content } // namespace content
...@@ -90,6 +90,8 @@ component("network_service") { ...@@ -90,6 +90,8 @@ component("network_service") {
"websocket.h", "websocket.h",
"websocket_factory.cc", "websocket_factory.cc",
"websocket_factory.h", "websocket_factory.h",
"websocket_throttler.cc",
"websocket_throttler.h",
] ]
} }
...@@ -163,7 +165,10 @@ source_set("tests") { ...@@ -163,7 +165,10 @@ source_set("tests") {
] ]
if (!is_ios) { if (!is_ios) {
sources += [ "proxy_resolver_factory_mojo_unittest.cc" ] sources += [
"proxy_resolver_factory_mojo_unittest.cc",
"websocket_throttler_unittest.cc",
]
} }
deps = [ deps = [
......
...@@ -136,7 +136,8 @@ ChannelState WebSocket::WebSocketEventHandler::OnAddChannelResponse( ...@@ -136,7 +136,8 @@ ChannelState WebSocket::WebSocketEventHandler::OnAddChannelResponse(
<< selected_protocol << "\"" << selected_protocol << "\""
<< " extensions=\"" << extensions << "\""; << " extensions=\"" << extensions << "\"";
impl_->delegate_->OnReceivedResponseFromServer(impl_); impl_->handshake_succeeded_ = true;
impl_->pending_connection_tracker_.OnCompleteHandshake();
impl_->client_->OnAddChannelResponse(selected_protocol, extensions); impl_->client_->OnAddChannelResponse(selected_protocol, extensions);
...@@ -288,14 +289,17 @@ ChannelState WebSocket::WebSocketEventHandler::OnSSLCertificateError( ...@@ -288,14 +289,17 @@ ChannelState WebSocket::WebSocketEventHandler::OnSSLCertificateError(
return WebSocketEventInterface::CHANNEL_ALIVE; return WebSocketEventInterface::CHANNEL_ALIVE;
} }
WebSocket::WebSocket(std::unique_ptr<Delegate> delegate, WebSocket::WebSocket(
network::mojom::WebSocketRequest request, std::unique_ptr<Delegate> delegate,
int child_id, network::mojom::WebSocketRequest request,
int frame_id, WebSocketThrottler::PendingConnection pending_connection_tracker,
url::Origin origin, int child_id,
base::TimeDelta delay) int frame_id,
url::Origin origin,
base::TimeDelta delay)
: delegate_(std::move(delegate)), : delegate_(std::move(delegate)),
binding_(this, std::move(request)), binding_(this, std::move(request)),
pending_connection_tracker_(std::move(pending_connection_tracker)),
delay_(delay), delay_(delay),
pending_flow_control_quota_(0), pending_flow_control_quota_(0),
child_id_(child_id), child_id_(child_id),
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding.h"
#include "net/websockets/websocket_event_interface.h" #include "net/websockets/websocket_event_interface.h"
#include "services/network/public/mojom/websocket.mojom.h" #include "services/network/public/mojom/websocket.mojom.h"
#include "services/network/websocket_throttler.h"
#include "url/origin.h" #include "url/origin.h"
class GURL; class GURL;
...@@ -44,7 +45,6 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocket ...@@ -44,7 +45,6 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocket
virtual ~Delegate() {} virtual ~Delegate() {}
virtual net::URLRequestContext* GetURLRequestContext() = 0; virtual net::URLRequestContext* GetURLRequestContext() = 0;
virtual void OnReceivedResponseFromServer(WebSocket* impl) = 0;
// This function may delete |impl|. // This function may delete |impl|.
virtual void OnLostConnectionToClient(WebSocket* impl) = 0; virtual void OnLostConnectionToClient(WebSocket* impl) = 0;
virtual void OnSSLCertificateError( virtual void OnSSLCertificateError(
...@@ -65,6 +65,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocket ...@@ -65,6 +65,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocket
WebSocket(std::unique_ptr<Delegate> delegate, WebSocket(std::unique_ptr<Delegate> delegate,
network::mojom::WebSocketRequest request, network::mojom::WebSocketRequest request,
WebSocketThrottler::PendingConnection pending_connection_tracker,
int child_id, int child_id,
int frame_id, int frame_id,
url::Origin origin, url::Origin origin,
...@@ -88,7 +89,6 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocket ...@@ -88,7 +89,6 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocket
void StartClosingHandshake(uint16_t code, const std::string& reason) override; void StartClosingHandshake(uint16_t code, const std::string& reason) override;
bool handshake_succeeded() const { return handshake_succeeded_; } bool handshake_succeeded() const { return handshake_succeeded_; }
void OnHandshakeSucceeded() { handshake_succeeded_ = true; }
protected: protected:
class WebSocketEventHandler; class WebSocketEventHandler;
...@@ -104,6 +104,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocket ...@@ -104,6 +104,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocket
network::mojom::WebSocketClientPtr client_; network::mojom::WebSocketClientPtr client_;
WebSocketThrottler::PendingConnection pending_connection_tracker_;
// The channel we use to send events to the network. // The channel we use to send events to the network.
std::unique_ptr<net::WebSocketChannel> channel_; std::unique_ptr<net::WebSocketChannel> channel_;
......
...@@ -26,8 +26,6 @@ class WebSocketFactory::Delegate final ...@@ -26,8 +26,6 @@ class WebSocketFactory::Delegate final
return factory_->context_->GetURLRequestContext(); return factory_->context_->GetURLRequestContext();
} }
void OnReceivedResponseFromServer(WebSocket* impl) override {}
void OnLostConnectionToClient(WebSocket* impl) override { void OnLostConnectionToClient(WebSocket* impl) override {
factory_->OnLostConnectionToClient(impl); factory_->OnLostConnectionToClient(impl);
} }
...@@ -100,10 +98,17 @@ void WebSocketFactory::CreateWebSocket(mojom::WebSocketRequest request, ...@@ -100,10 +98,17 @@ void WebSocketFactory::CreateWebSocket(mojom::WebSocketRequest request,
int32_t process_id, int32_t process_id,
int32_t render_frame_id, int32_t render_frame_id,
const url::Origin& origin) { const url::Origin& origin) {
base::TimeDelta delay; if (throttler_.HasTooManyPendingConnections(process_id)) {
// Too many websockets!
request.ResetWithReason(
mojom::WebSocket::kInsufficientResources,
"Error in connection establishment: net::ERR_INSUFFICIENT_RESOURCES");
return;
}
connections_.insert(std::make_unique<WebSocket>( connections_.insert(std::make_unique<WebSocket>(
std::make_unique<Delegate>(this, process_id), std::move(request), std::make_unique<Delegate>(this, process_id), std::move(request),
process_id, render_frame_id, origin, delay)); throttler_.IssuePendingConnectionTracker(process_id), process_id,
render_frame_id, origin, throttler_.CalculateDelay(process_id)));
} }
void WebSocketFactory::OnLostConnectionToClient(WebSocket* impl) { void WebSocketFactory::OnLostConnectionToClient(WebSocket* impl) {
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding.h"
#include "services/network/public/mojom/websocket.mojom.h" #include "services/network/public/mojom/websocket.mojom.h"
#include "services/network/websocket.h" #include "services/network/websocket.h"
#include "services/network/websocket_throttler.h"
namespace url { namespace url {
class Origin; class Origin;
...@@ -40,6 +41,8 @@ class WebSocketFactory final : public base::SupportsWeakPtr<WebSocketFactory> { ...@@ -40,6 +41,8 @@ class WebSocketFactory final : public base::SupportsWeakPtr<WebSocketFactory> {
// The connections held by this factory. // The connections held by this factory.
std::set<std::unique_ptr<WebSocket>, base::UniquePtrComparator> connections_; std::set<std::unique_ptr<WebSocket>, base::UniquePtrComparator> connections_;
WebSocketThrottler throttler_;
// |context_| outlives this object. // |context_| outlives this object.
NetworkContext* const context_; NetworkContext* const context_;
......
// Copyright 2018 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 "services/network/websocket_throttler.h"
#include <algorithm>
#include "base/rand_util.h"
namespace network {
constexpr int WebSocketPerProcessThrottler::kMaxPendingWebSocketConnections;
WebSocketPerProcessThrottler::PendingConnection::PendingConnection(
base::WeakPtr<WebSocketPerProcessThrottler> throttler)
: throttler_(std::move(throttler)) {
DCHECK(throttler_);
++throttler_->num_pending_connections_;
}
WebSocketPerProcessThrottler::PendingConnection::PendingConnection(
PendingConnection&& other)
: throttler_(std::move(other.throttler_)) {
other.throttler_ = nullptr;
}
WebSocketPerProcessThrottler::PendingConnection::~PendingConnection() {
if (!throttler_)
return;
--throttler_->num_pending_connections_;
++throttler_->num_current_failed_connections_;
}
void WebSocketPerProcessThrottler::PendingConnection::OnCompleteHandshake() {
DCHECK(throttler_);
--throttler_->num_pending_connections_;
++throttler_->num_current_succeeded_connections_;
throttler_ = nullptr;
}
WebSocketPerProcessThrottler::WebSocketPerProcessThrottler() {}
WebSocketPerProcessThrottler::~WebSocketPerProcessThrottler() {}
base::TimeDelta WebSocketPerProcessThrottler::CalculateDelay() const {
int64_t f =
num_previous_failed_connections_ + num_current_failed_connections_;
int64_t s =
num_previous_succeeded_connections_ + num_current_succeeded_connections_;
int p = num_pending_connections_;
return base::TimeDelta::FromMilliseconds(
base::RandInt(1000, 5000) *
(1 << std::min(p + f / (s + 1), INT64_C(16))) / 65536);
return base::TimeDelta();
}
WebSocketPerProcessThrottler::PendingConnection
WebSocketPerProcessThrottler::IssuePendingConnectionTracker() {
return PendingConnection(AsWeakPtr());
}
bool WebSocketPerProcessThrottler::IsClean() const {
return num_pending_connections_ == 0 &&
num_current_succeeded_connections_ == 0 &&
num_previous_succeeded_connections_ == 0 &&
num_current_failed_connections_ == 0 &&
num_previous_succeeded_connections_ == 0;
}
void WebSocketPerProcessThrottler::Roll() {
num_previous_succeeded_connections_ = num_current_succeeded_connections_;
num_previous_failed_connections_ = num_current_failed_connections_;
num_current_succeeded_connections_ = 0;
num_current_failed_connections_ = 0;
}
WebSocketThrottler::WebSocketThrottler() {}
WebSocketThrottler::~WebSocketThrottler() {}
bool WebSocketThrottler::HasTooManyPendingConnections(int process_id) const {
auto it = per_process_throttlers_.find(process_id);
if (it == per_process_throttlers_.end())
return false;
return it->second->HasTooManyPendingConnections();
}
base::TimeDelta WebSocketThrottler::CalculateDelay(int process_id) const {
auto it = per_process_throttlers_.find(process_id);
if (it == per_process_throttlers_.end())
return base::TimeDelta();
return it->second->CalculateDelay();
}
WebSocketThrottler::PendingConnection
WebSocketThrottler::IssuePendingConnectionTracker(int process_id) {
auto it = per_process_throttlers_.find(process_id);
if (it == per_process_throttlers_.end()) {
it = per_process_throttlers_
.insert(std::make_pair(
process_id, std::make_unique<WebSocketPerProcessThrottler>()))
.first;
}
if (!throttling_period_timer_.IsRunning()) {
throttling_period_timer_.Start(FROM_HERE, base::TimeDelta::FromMinutes(2),
this, &WebSocketThrottler::OnTimer);
}
return it->second->IssuePendingConnectionTracker();
}
void WebSocketThrottler::OnTimer() {
auto it = per_process_throttlers_.begin();
while (it != per_process_throttlers_.end()) {
it->second->Roll();
if (it->second->IsClean()) {
// We don't need the entry. Erase it.
it = per_process_throttlers_.erase(it);
} else {
++it;
}
}
if (per_process_throttlers_.empty())
throttling_period_timer_.Stop();
}
} // namespace network
// Copyright 2018 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 SERVICES_NETWORK_WEBSOCKET_THROTTLER_H_
#define SERVICES_NETWORK_WEBSOCKET_THROTTLER_H_
#include <stdint.h>
#include <map>
#include <memory>
#include "base/component_export.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
namespace network {
// WebSocketPerProcessThrottler provies a throttling functionality per
// renderer process. See https://goo.gl/tldFNn.
class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocketPerProcessThrottler final
: public base::SupportsWeakPtr<WebSocketPerProcessThrottler> {
public:
// A PendingConnection represents a connection that has not finished a
// handshake.
//
// Destroying a PendingConnection whose OnCompleteHandshake has not been
// called represents a handshake failure (including going away during
// handshake).
class COMPONENT_EXPORT(NETWORK_SERVICE) PendingConnection final {
public:
// |throttler| cannot be null.
explicit PendingConnection(
base::WeakPtr<WebSocketPerProcessThrottler> throttler);
PendingConnection(PendingConnection&& other);
~PendingConnection();
// Called when the hansdhake finishes sucessfully.
void OnCompleteHandshake();
private:
base::WeakPtr<WebSocketPerProcessThrottler> throttler_;
DISALLOW_COPY_AND_ASSIGN(PendingConnection);
};
WebSocketPerProcessThrottler();
~WebSocketPerProcessThrottler();
// Returns if there are too many pending connections.
bool HasTooManyPendingConnections() const {
return num_pending_connections_ >= kMaxPendingWebSocketConnections;
}
// Returns the delay which should be used to throttle opening websocket
// connections.
base::TimeDelta CalculateDelay() const;
// Issues an object which represents a pending connection.
PendingConnection IssuePendingConnectionTracker();
// Returns true if this throttler is clean, i.e., we can restore the internal
// state by simply creating a new object.
bool IsClean() const;
// Copies the succeeded / failed counters for the current period to the
// ones for the previous period, and zeroes them.
void Roll();
int64_t num_pending_connections() const { return num_pending_connections_; }
int64_t num_current_succeeded_connections() const {
return num_current_succeeded_connections_;
}
int64_t num_previous_succeeded_connections() const {
return num_previous_succeeded_connections_;
}
int64_t num_current_failed_connections() const {
return num_current_failed_connections_;
}
int64_t num_previous_failed_connections() const {
return num_previous_failed_connections_;
}
private:
// The current number of pending connections.
int num_pending_connections_ = 0;
// The number of handshakes that failed in the clurrent and previous time
// period.
int64_t num_current_succeeded_connections_ = 0;
int64_t num_previous_succeeded_connections_ = 0;
// The number of handshakes that succeeded in the current and previous time
// period.
int64_t num_current_failed_connections_ = 0;
int64_t num_previous_failed_connections_ = 0;
static constexpr int kMaxPendingWebSocketConnections = 255;
DISALLOW_COPY_AND_ASSIGN(WebSocketPerProcessThrottler);
};
// This class is for throttling WebSocket connections. WebSocketThrottler is
// a set of per-renderer throttlers.
// This class is only used in the network service. content::WebSocketManager
// uses WebSocketPerProcessThrottler directly.
class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocketThrottler final {
public:
using PendingConnection = WebSocketPerProcessThrottler::PendingConnection;
WebSocketThrottler();
~WebSocketThrottler();
// Returns true if there are too many pending connections for |process_id|.
bool HasTooManyPendingConnections(int process_id) const;
// Calculates connection delay for |process_id|.
base::TimeDelta CalculateDelay(int process_id) const;
// Returns a pending connection for |process_id|. This function can be called
// only when |HasTooManyPendingConnections(process_id)| is false.
PendingConnection IssuePendingConnectionTracker(int process_id);
size_t GetSizeForTesting() const { return per_process_throttlers_.size(); }
private:
void OnTimer();
std::map<int, std::unique_ptr<WebSocketPerProcessThrottler>>
per_process_throttlers_;
base::RepeatingTimer throttling_period_timer_;
DISALLOW_COPY_AND_ASSIGN(WebSocketThrottler);
};
} // namespace network
#endif // SERVICES_NETWORK_WEBSOCKET_THROTTLER_H_
This diff is collapsed.
...@@ -87,8 +87,6 @@ crbug.com/764474 plugins/plugin-document-back-forward.html [ Crash Failure Timeo ...@@ -87,8 +87,6 @@ crbug.com/764474 plugins/plugin-document-back-forward.html [ Crash Failure Timeo
# Started failing @ r529490 # Started failing @ r529490
Bug(none) virtual/layout_ng/fast/inline/positioned-object-between-replaced-elements.html [ Failure ] Bug(none) virtual/layout_ng/fast/inline/positioned-object-between-replaced-elements.html [ Failure ]
# http://crbug.com/721400 get WebSockets working with network service. crbug.com/825687 http/tests/devtools/websocket/websocket-handshake.js [ Failure ]
crbug.com/721400 http/tests/websocket/multiple-connections-throttled.html [ Failure ]
crbug.com/721400 http/tests/devtools/websocket/websocket-handshake.js [ Failure ]
crbug.com/816556 external/wpt/html/semantics/text-level-semantics/the-a-element/a-download-click-404.html [ Failure ] crbug.com/816556 external/wpt/html/semantics/text-level-semantics/the-a-element/a-download-click-404.html [ Failure ]
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