// Copyright (c) 2011 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 "net/websockets/websocket_throttle.h" #include <string> #include "base/hash_tables.h" #include "base/memory/ref_counted.h" #include "base/memory/singleton.h" #include "base/message_loop.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "net/base/io_buffer.h" #include "net/base/sys_addrinfo.h" #include "net/socket_stream/socket_stream.h" #include "net/websockets/websocket_job.h" namespace net { static std::string AddrinfoToHashkey(const struct addrinfo* addrinfo) { switch (addrinfo->ai_family) { case AF_INET: { const struct sockaddr_in* const addr = reinterpret_cast<const sockaddr_in*>(addrinfo->ai_addr); return base::StringPrintf("%d:%s", addrinfo->ai_family, base::HexEncode(&addr->sin_addr, 4).c_str()); } case AF_INET6: { const struct sockaddr_in6* const addr6 = reinterpret_cast<const sockaddr_in6*>(addrinfo->ai_addr); return base::StringPrintf( "%d:%s", addrinfo->ai_family, base::HexEncode(&addr6->sin6_addr, sizeof(addr6->sin6_addr)).c_str()); } default: return base::StringPrintf("%d:%s", addrinfo->ai_family, base::HexEncode(addrinfo->ai_addr, addrinfo->ai_addrlen).c_str()); } } WebSocketThrottle::WebSocketThrottle() { } WebSocketThrottle::~WebSocketThrottle() { DCHECK(queue_.empty()); DCHECK(addr_map_.empty()); } // static WebSocketThrottle* WebSocketThrottle::GetInstance() { return Singleton<WebSocketThrottle>::get(); } void WebSocketThrottle::PutInQueue(WebSocketJob* job) { queue_.push_back(job); const AddressList& address_list = job->address_list(); base::hash_set<std::string> address_set; for (const struct addrinfo* addrinfo = address_list.head(); addrinfo != NULL; addrinfo = addrinfo->ai_next) { std::string addrkey = AddrinfoToHashkey(addrinfo); // If |addrkey| is already processed, don't do it again. if (address_set.find(addrkey) != address_set.end()) continue; address_set.insert(addrkey); ConnectingAddressMap::iterator iter = addr_map_.find(addrkey); if (iter == addr_map_.end()) { ConnectingQueue* queue = new ConnectingQueue(); queue->push_back(job); addr_map_[addrkey] = queue; } else { iter->second->push_back(job); job->SetWaiting(); DVLOG(1) << "Waiting on " << addrkey; } } } void WebSocketThrottle::RemoveFromQueue(WebSocketJob* job) { bool in_queue = false; for (ConnectingQueue::iterator iter = queue_.begin(); iter != queue_.end(); ++iter) { if (*iter == job) { queue_.erase(iter); in_queue = true; break; } } if (!in_queue) return; const AddressList& address_list = job->address_list(); base::hash_set<std::string> address_set; for (const struct addrinfo* addrinfo = address_list.head(); addrinfo != NULL; addrinfo = addrinfo->ai_next) { std::string addrkey = AddrinfoToHashkey(addrinfo); // If |addrkey| is already processed, don't do it again. if (address_set.find(addrkey) != address_set.end()) continue; address_set.insert(addrkey); ConnectingAddressMap::iterator iter = addr_map_.find(addrkey); DCHECK(iter != addr_map_.end()); ConnectingQueue* queue = iter->second; // Job may not be front of queue when job is closed early while waiting. for (ConnectingQueue::iterator iter = queue->begin(); iter != queue->end(); ++iter) { if (*iter == job) { queue->erase(iter); break; } } if (queue->empty()) { delete queue; addr_map_.erase(iter); } } } void WebSocketThrottle::WakeupSocketIfNecessary() { for (ConnectingQueue::iterator iter = queue_.begin(); iter != queue_.end(); ++iter) { WebSocketJob* job = *iter; if (!job->IsWaiting()) continue; bool should_wakeup = true; const AddressList& address_list = job->address_list(); for (const struct addrinfo* addrinfo = address_list.head(); addrinfo != NULL; addrinfo = addrinfo->ai_next) { std::string addrkey = AddrinfoToHashkey(addrinfo); ConnectingAddressMap::iterator iter = addr_map_.find(addrkey); DCHECK(iter != addr_map_.end()); ConnectingQueue* queue = iter->second; if (job != queue->front()) { should_wakeup = false; break; } } if (should_wakeup) job->Wakeup(); } } } // namespace net