blob: b3132bee17e24b492066d4164a9acb678f790069 [file] [log] [blame]
// 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/functional/callback.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::EndpointLock::EndpointLock(
WebSocketEndpointLockManager* websocket_endpoint_lock_manager,
const IPEndPoint& endpoint)
: websocket_endpoint_lock_manager_(websocket_endpoint_lock_manager),
endpoint_(endpoint) {}
WebSocketEndpointLockManager::EndpointLock::~EndpointLock() {
if (next()) {
// If in queue, remove `this`.
DCHECK(previous());
RemoveFromList();
}
// Release lock if held.
if (lock_info_) {
websocket_endpoint_lock_manager_->UnlockEndpointInternal(endpoint_,
*lock_info_);
}
}
int WebSocketEndpointLockManager::EndpointLock::LockEndpoint(
base::OnceClosure lock_callback) {
DCHECK(!lock_callback_);
DCHECK(lock_callback);
int result = websocket_endpoint_lock_manager_->LockEndpoint(endpoint_, this);
if (result == ERR_IO_PENDING) {
lock_callback_ = std::move(lock_callback);
}
return result;
}
void WebSocketEndpointLockManager::EndpointLock::GotEndpointLock() {
DCHECK(lock_callback_);
std::move(lock_callback_).Run();
}
WebSocketEndpointLockManager::WebSocketEndpointLockManager()
: unlock_delay_(base::Milliseconds(kUnlockDelayInMs)) {}
WebSocketEndpointLockManager::~WebSocketEndpointLockManager() {
DCHECK_EQ(lock_info_map_.size(), pending_unlock_count_);
}
void WebSocketEndpointLockManager::UnlockEndpoint(const IPEndPoint& endpoint) {
auto lock_info_it = lock_info_map_.find(endpoint);
// Nothing to do if the lock is not held. This is not an error.
if (lock_info_it == lock_info_map_.end()) {
return;
}
UnlockEndpointInternal(endpoint, lock_info_it->second);
}
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() = default;
WebSocketEndpointLockManager::LockInfo::~LockInfo() {
DCHECK(!endpoint_lock);
}
WebSocketEndpointLockManager::LockInfo::LockInfo(const LockInfo& rhs)
: endpoint_lock(rhs.endpoint_lock) {
DCHECK(!rhs.queue);
}
int WebSocketEndpointLockManager::LockEndpoint(const IPEndPoint& endpoint,
EndpointLock* endpoint_lock) {
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>();
// The endpoint is now locked by `endpoint_lock`.
SetLock(&lock_info_in_map, endpoint_lock);
return OK;
}
DVLOG(3) << "Waiting for endpoint " << endpoint.ToString();
lock_info_in_map.queue->Append(endpoint_lock);
return ERR_IO_PENDING;
}
void WebSocketEndpointLockManager::UnlockEndpointInternal(
const IPEndPoint& endpoint,
LockInfo& lock_info) {
ClearLock(lock_info);
UnlockEndpointAfterDelay(endpoint);
}
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.endpoint_lock);
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";
EndpointLock* endpoint_lock = queue->head()->value();
// The endpoint is now locked by `endpoint_lock`.
SetLock(&lock_info_it->second, endpoint_lock);
endpoint_lock->RemoveFromList();
endpoint_lock->GotEndpointLock();
}
void WebSocketEndpointLockManager::SetLock(LockInfo* lock_info,
EndpointLock* endpoint_lock) {
DCHECK(endpoint_lock);
DCHECK(!lock_info->endpoint_lock);
DCHECK(!endpoint_lock->lock_info_);
lock_info->endpoint_lock = endpoint_lock;
endpoint_lock->lock_info_ = lock_info;
}
void WebSocketEndpointLockManager::ClearLock(LockInfo& lock_info) {
DCHECK(!lock_info.endpoint_lock ||
&lock_info == lock_info.endpoint_lock->lock_info_);
lock_info.endpoint_lock->lock_info_ = nullptr;
lock_info.endpoint_lock = nullptr;
}
} // namespace net