| // 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() |
| : weak_factory_(this) {} |
| 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); |
| } |
| |
| WebSocketPerProcessThrottler::PendingConnection |
| WebSocketPerProcessThrottler::IssuePendingConnectionTracker() { |
| return PendingConnection(weak_factory_.GetWeakPtr()); |
| } |
| |
| 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 |