152 lines
4.8 KiB
C++
152 lines
4.8 KiB
C++
// Copyright 2014 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/socket/websocket_endpoint_lock_manager.h"
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "base/functional/bind.h"
|
|
#include "base/location.h"
|
|
#include "base/logging.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "net/base/net_errors.h"
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
// This delay prevents DoS attacks.
|
|
// TODO(ricea): Replace this with randomised truncated exponential backoff.
|
|
// See crbug.com/377613.
|
|
const int kUnlockDelayInMs = 10;
|
|
|
|
} // namespace
|
|
|
|
WebSocketEndpointLockManager::Waiter::~Waiter() {
|
|
if (next()) {
|
|
DCHECK(previous());
|
|
RemoveFromList();
|
|
}
|
|
}
|
|
|
|
WebSocketEndpointLockManager::LockReleaser::LockReleaser(
|
|
WebSocketEndpointLockManager* websocket_endpoint_lock_manager,
|
|
IPEndPoint endpoint)
|
|
: websocket_endpoint_lock_manager_(websocket_endpoint_lock_manager),
|
|
endpoint_(endpoint) {
|
|
websocket_endpoint_lock_manager->RegisterLockReleaser(this, endpoint);
|
|
}
|
|
|
|
WebSocketEndpointLockManager::LockReleaser::~LockReleaser() {
|
|
if (websocket_endpoint_lock_manager_) {
|
|
websocket_endpoint_lock_manager_->UnlockEndpoint(endpoint_);
|
|
}
|
|
}
|
|
|
|
WebSocketEndpointLockManager::WebSocketEndpointLockManager()
|
|
: unlock_delay_(base::Milliseconds(kUnlockDelayInMs)) {}
|
|
|
|
WebSocketEndpointLockManager::~WebSocketEndpointLockManager() {
|
|
DCHECK_EQ(lock_info_map_.size(), pending_unlock_count_);
|
|
}
|
|
|
|
int WebSocketEndpointLockManager::LockEndpoint(const IPEndPoint& endpoint,
|
|
Waiter* waiter) {
|
|
LockInfoMap::value_type insert_value(endpoint, LockInfo());
|
|
std::pair<LockInfoMap::iterator, bool> rv =
|
|
lock_info_map_.insert(insert_value);
|
|
LockInfo& lock_info_in_map = rv.first->second;
|
|
if (rv.second) {
|
|
DVLOG(3) << "Locking endpoint " << endpoint.ToString();
|
|
lock_info_in_map.queue = std::make_unique<LockInfo::WaiterQueue>();
|
|
return OK;
|
|
}
|
|
DVLOG(3) << "Waiting for endpoint " << endpoint.ToString();
|
|
lock_info_in_map.queue->Append(waiter);
|
|
return ERR_IO_PENDING;
|
|
}
|
|
|
|
void WebSocketEndpointLockManager::UnlockEndpoint(const IPEndPoint& endpoint) {
|
|
auto lock_info_it = lock_info_map_.find(endpoint);
|
|
if (lock_info_it == lock_info_map_.end())
|
|
return;
|
|
LockReleaser* lock_releaser = lock_info_it->second.lock_releaser;
|
|
if (lock_releaser) {
|
|
lock_info_it->second.lock_releaser = nullptr;
|
|
lock_releaser->websocket_endpoint_lock_manager_ = nullptr;
|
|
}
|
|
UnlockEndpointAfterDelay(endpoint);
|
|
}
|
|
|
|
bool WebSocketEndpointLockManager::IsEmpty() const {
|
|
return lock_info_map_.empty();
|
|
}
|
|
|
|
base::TimeDelta WebSocketEndpointLockManager::SetUnlockDelayForTesting(
|
|
base::TimeDelta new_delay) {
|
|
base::TimeDelta old_delay = unlock_delay_;
|
|
unlock_delay_ = new_delay;
|
|
return old_delay;
|
|
}
|
|
|
|
WebSocketEndpointLockManager::LockInfo::LockInfo() : lock_releaser(nullptr) {}
|
|
WebSocketEndpointLockManager::LockInfo::~LockInfo() {
|
|
DCHECK(!lock_releaser);
|
|
}
|
|
|
|
WebSocketEndpointLockManager::LockInfo::LockInfo(const LockInfo& rhs)
|
|
: lock_releaser(rhs.lock_releaser) {
|
|
DCHECK(!rhs.queue);
|
|
}
|
|
|
|
void WebSocketEndpointLockManager::RegisterLockReleaser(
|
|
LockReleaser* lock_releaser,
|
|
IPEndPoint endpoint) {
|
|
DCHECK(lock_releaser);
|
|
auto lock_info_it = lock_info_map_.find(endpoint);
|
|
CHECK(lock_info_it != lock_info_map_.end());
|
|
DCHECK(!lock_info_it->second.lock_releaser);
|
|
lock_info_it->second.lock_releaser = lock_releaser;
|
|
DVLOG(3) << "Registered (LockReleaser*)" << lock_releaser << " for "
|
|
<< endpoint.ToString();
|
|
}
|
|
|
|
void WebSocketEndpointLockManager::UnlockEndpointAfterDelay(
|
|
const IPEndPoint& endpoint) {
|
|
DVLOG(3) << "Delaying " << unlock_delay_.InMilliseconds()
|
|
<< "ms before unlocking endpoint " << endpoint.ToString();
|
|
++pending_unlock_count_;
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
|
|
FROM_HERE,
|
|
base::BindOnce(&WebSocketEndpointLockManager::DelayedUnlockEndpoint,
|
|
weak_factory_.GetWeakPtr(), endpoint),
|
|
unlock_delay_);
|
|
}
|
|
|
|
void WebSocketEndpointLockManager::DelayedUnlockEndpoint(
|
|
const IPEndPoint& endpoint) {
|
|
auto lock_info_it = lock_info_map_.find(endpoint);
|
|
DCHECK_GT(pending_unlock_count_, 0U);
|
|
--pending_unlock_count_;
|
|
if (lock_info_it == lock_info_map_.end())
|
|
return;
|
|
DCHECK(!lock_info_it->second.lock_releaser);
|
|
LockInfo::WaiterQueue* queue = lock_info_it->second.queue.get();
|
|
DCHECK(queue);
|
|
if (queue->empty()) {
|
|
DVLOG(3) << "Unlocking endpoint " << lock_info_it->first.ToString();
|
|
lock_info_map_.erase(lock_info_it);
|
|
return;
|
|
}
|
|
|
|
DVLOG(3) << "Unlocking endpoint " << lock_info_it->first.ToString()
|
|
<< " and activating next waiter";
|
|
Waiter* next_job = queue->head()->value();
|
|
next_job->RemoveFromList();
|
|
next_job->GotEndpointLock();
|
|
}
|
|
|
|
} // namespace net
|