blob: 6984564fda716b0ddd2eba4c14bdd4a65b8738bf [file] [log] [blame]
// Copyright 2013 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 "content/browser/websockets/websocket_manager.h"
#include <algorithm>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/rand_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_observer.h"
#include "services/service_manager/public/cpp/interface_registry.h"
namespace content {
namespace {
const char kWebSocketManagerKeyName[] = "web_socket_manager";
// Max number of pending connections per WebSocketManager used for per-renderer
// WebSocket throttling.
const int kMaxPendingWebSocketConnections = 255;
} // namespace
class WebSocketManager::Handle : public base::SupportsUserData::Data,
public RenderProcessHostObserver {
public:
explicit Handle(WebSocketManager* manager) : manager_(manager) {}
~Handle() override {
DCHECK(!manager_) << "Should have received RenderProcessHostDestroyed";
}
WebSocketManager* manager() const { return manager_; }
// The network stack could be shutdown after this notification, so be sure to
// stop using it before then.
void RenderProcessHostDestroyed(RenderProcessHost* host) override {
BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, manager_);
manager_ = nullptr;
}
private:
WebSocketManager* manager_;
};
// static
void WebSocketManager::CreateWebSocket(int process_id, int frame_id,
blink::mojom::WebSocketRequest request) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RenderProcessHost* host = RenderProcessHost::FromID(process_id);
DCHECK(host);
// Maintain a WebSocketManager per RenderProcessHost. While the instance of
// WebSocketManager is allocated on the UI thread, it must only be used and
// deleted from the IO thread.
Handle* handle =
static_cast<Handle*>(host->GetUserData(kWebSocketManagerKeyName));
if (!handle) {
handle = new Handle(
new WebSocketManager(process_id, host->GetStoragePartition()));
host->SetUserData(kWebSocketManagerKeyName, handle);
host->AddObserver(handle);
} else {
DCHECK(handle->manager());
}
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&WebSocketManager::DoCreateWebSocket,
base::Unretained(handle->manager()),
frame_id,
base::Passed(&request)));
}
WebSocketManager::WebSocketManager(int process_id,
StoragePartition* storage_partition)
: process_id_(process_id),
storage_partition_(storage_partition),
num_pending_connections_(0),
num_current_succeeded_connections_(0),
num_previous_succeeded_connections_(0),
num_current_failed_connections_(0),
num_previous_failed_connections_(0) {}
WebSocketManager::~WebSocketManager() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
for (auto impl : impls_) {
impl->GoAway();
delete impl;
}
}
void WebSocketManager::DoCreateWebSocket(
int frame_id,
blink::mojom::WebSocketRequest request) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (num_pending_connections_ >= kMaxPendingWebSocketConnections) {
// Too many websockets!
request.ResetWithReason(
blink::mojom::WebSocket::kInsufficientResources,
"Error in connection establishment: net::ERR_INSUFFICIENT_RESOURCES");
return;
}
// Keep all WebSocketImpls alive until either the client drops its
// connection (see OnLostConnectionToClient) or we need to shutdown.
impls_.insert(CreateWebSocketImpl(this, std::move(request), process_id_,
frame_id, CalculateDelay()));
++num_pending_connections_;
if (!throttling_period_timer_.IsRunning()) {
throttling_period_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMinutes(2),
this,
&WebSocketManager::ThrottlingPeriodTimerCallback);
}
}
// 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() {
num_previous_failed_connections_ = num_current_failed_connections_;
num_current_failed_connections_ = 0;
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();
}
}
WebSocketImpl* WebSocketManager::CreateWebSocketImpl(
WebSocketImpl::Delegate* delegate,
blink::mojom::WebSocketRequest request,
int child_id,
int frame_id,
base::TimeDelta delay) {
return new WebSocketImpl(delegate, std::move(request), child_id, frame_id,
delay);
}
int WebSocketManager::GetClientProcessId() {
return process_id_;
}
StoragePartition* WebSocketManager::GetStoragePartition() {
return storage_partition_;
}
void WebSocketManager::OnReceivedResponseFromServer(WebSocketImpl* 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(WebSocketImpl* impl) {
// 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();
impls_.erase(impl);
delete impl;
}
} // namespace content