From 6d1f0aed52b890e4f4bfa2281894df4d423cf1c6 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 3 Aug 2020 11:36:49 -0700 Subject: [PATCH] src: add SocketAddressLRU Utility Adds a LRU cache for information associated with a SocketAddress. PR-URL: https://github.com/nodejs/node/pull/34618 Reviewed-By: Anna Henningsen Reviewed-By: Rich Trott --- src/node_sockaddr-inl.h | 67 ++++++++++++++++++++++++++++++++++ src/node_sockaddr.h | 36 +++++++++++++++++++ test/cctest/test_sockaddr.cc | 70 ++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+) diff --git a/src/node_sockaddr-inl.h b/src/node_sockaddr-inl.h index a9d0ed061a126c..c8b985aedda2f6 100644 --- a/src/node_sockaddr-inl.h +++ b/src/node_sockaddr-inl.h @@ -7,6 +7,7 @@ #include "node_internals.h" #include "node_sockaddr.h" #include "util-inl.h" +#include "memory_tracker-inl.h" #include @@ -164,6 +165,72 @@ bool SocketAddress::operator==(const SocketAddress& other) const { bool SocketAddress::operator!=(const SocketAddress& other) const { return !(*this == other); } + +template +SocketAddressLRU::SocketAddressLRU( + size_t max_size) + : max_size_(max_size) {} + +template +typename T::Type* SocketAddressLRU::Peek( + const SocketAddress& address) const { + auto it = map_.find(address); + return it == std::end(map_) ? nullptr : &it->second->second; +} + +template +void SocketAddressLRU::CheckExpired() { + auto it = list_.rbegin(); + while (it != list_.rend()) { + if (T::CheckExpired(it->first, it->second)) { + map_.erase(it->first); + list_.pop_back(); + it = list_.rbegin(); + continue; + } else { + break; + } + } +} + +template +void SocketAddressLRU::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackFieldWithSize("list", size() * sizeof(Pair)); +} + +// If an item already exists for the given address, bump up it's +// position in the LRU list and return it. If the item does not +// exist, create it. If an item is created, check the size of the +// cache and adjust if necessary. Whether the item exists or not, +// purge expired items. +template +typename T::Type* SocketAddressLRU::Upsert( + const SocketAddress& address) { + + auto on_exit = OnScopeLeave([&]() { CheckExpired(); }); + + auto it = map_.find(address); + if (it != std::end(map_)) { + list_.splice(list_.begin(), list_, it->second); + T::Touch(it->first, &it->second->second); + return &it->second->second; + } + + list_.push_front(Pair(address, { })); + map_[address] = list_.begin(); + T::Touch(list_.begin()->first, &list_.begin()->second); + + // Drop the last item in the list if we are + // over the size limit... + if (map_.size() > max_size_) { + auto last = list_.end(); + map_.erase((--last)->first); + list_.pop_back(); + } + + return &map_[address]->second; +} + } // namespace node #endif // NODE_WANT_INTERNALS diff --git a/src/node_sockaddr.h b/src/node_sockaddr.h index c0d006a4d6e681..5d20487f93dbe8 100644 --- a/src/node_sockaddr.h +++ b/src/node_sockaddr.h @@ -9,6 +9,7 @@ #include "v8.h" #include +#include #include namespace node { @@ -116,6 +117,41 @@ class SocketAddress : public MemoryRetainer { sockaddr_storage address_; }; +template +class SocketAddressLRU : public MemoryRetainer { + public: + using Type = typename T::Type; + + inline explicit SocketAddressLRU(size_t max_size); + + // If the item already exists, returns a reference to + // the existing item, adjusting items position in the + // LRU. If the item does not exist, emplaces the item + // and returns the new item. + Type* Upsert(const SocketAddress& address); + + // Returns a reference to the item if it exists, or + // nullptr. The position in the LRU is not modified. + Type* Peek(const SocketAddress& address) const; + + size_t size() const { return map_.size(); } + size_t max_size() const { return max_size_; } + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(SocketAddressLRU) + SET_SELF_SIZE(SocketAddressLRU) + + private: + using Pair = std::pair; + using Iterator = typename std::list::iterator; + + void CheckExpired(); + + std::list list_; + SocketAddress::Map map_; + size_t max_size_; +}; + } // namespace node #endif // NOE_WANT_INTERNALS diff --git a/test/cctest/test_sockaddr.cc b/test/cctest/test_sockaddr.cc index 8c23463f11d2d1..9abcd8ba819c64 100644 --- a/test/cctest/test_sockaddr.cc +++ b/test/cctest/test_sockaddr.cc @@ -2,6 +2,7 @@ #include "gtest/gtest.h" using node::SocketAddress; +using node::SocketAddressLRU; TEST(SocketAddress, SocketAddress) { CHECK(SocketAddress::is_numeric_host("123.123.123.123")); @@ -55,3 +56,72 @@ TEST(SocketAddress, SocketAddressIPv6) { addr.set_flow_label(12345); CHECK_EQ(addr.flow_label(), 12345); } + +TEST(SocketAddressLRU, SocketAddressLRU) { + struct Foo { + int c; + bool expired; + }; + + struct FooLRUTraits { + using Type = Foo; + + static bool CheckExpired(const SocketAddress& address, const Type& type) { + return type.expired; + } + + static void Touch(const SocketAddress& address, Type* type) { + type->expired = false; + } + }; + + SocketAddressLRU lru(2); + + sockaddr_storage storage[4]; + + SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage[0]); + SocketAddress::ToSockAddr(AF_INET, "123.123.123.124", 443, &storage[1]); + SocketAddress::ToSockAddr(AF_INET, "123.123.123.125", 443, &storage[2]); + SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage[3]); + + SocketAddress addr1(reinterpret_cast(&storage[0])); + SocketAddress addr2(reinterpret_cast(&storage[1])); + SocketAddress addr3(reinterpret_cast(&storage[2])); + SocketAddress addr4(reinterpret_cast(&storage[3])); + + Foo* foo = lru.Upsert(addr1); + CHECK_NOT_NULL(foo); + CHECK_EQ(foo->c, 0); + CHECK_EQ(foo->expired, false); + + foo->c = 1; + foo->expired = true; + + foo = lru.Upsert(addr1); + CHECK_NOT_NULL(lru.Peek(addr1)); + CHECK_EQ(lru.Peek(addr1), lru.Peek(addr4)); + CHECK_EQ(lru.Peek(addr1)->c, 1); + CHECK_EQ(lru.Peek(addr1)->expired, false); + CHECK_EQ(lru.size(), 1); + + foo = lru.Upsert(addr2); + foo->c = 2; + foo->expired = true; + CHECK_NOT_NULL(lru.Peek(addr2)); + CHECK_EQ(lru.Peek(addr2)->c, 2); + CHECK_EQ(lru.size(), 2); + + foo->expired = true; + + foo = lru.Upsert(addr3); + foo->c = 3; + foo->expired = false; + CHECK_NOT_NULL(lru.Peek(addr3)); + CHECK_EQ(lru.Peek(addr3)->c, 3); + CHECK_EQ(lru.size(), 1); + + // addr1 was removed because we exceeded size. + // addr2 was removed because it was expired. + CHECK_NULL(lru.Peek(addr1)); + CHECK_NULL(lru.Peek(addr2)); +}