Skip to content

Commit

Permalink
src: add SocketAddressLRU Utility
Browse files Browse the repository at this point in the history
Adds a LRU cache for information associated with a SocketAddress.

PR-URL: #34618
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Rich Trott <[email protected]>
  • Loading branch information
jasnell authored and targos committed Aug 8, 2021
1 parent 6ca3489 commit 6973a54
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 0 deletions.
67 changes: 67 additions & 0 deletions src/node_sockaddr-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "node_internals.h"
#include "node_sockaddr.h"
#include "util-inl.h"
#include "memory_tracker-inl.h"

#include <string>

Expand Down Expand Up @@ -164,6 +165,72 @@ bool SocketAddress::operator==(const SocketAddress& other) const {
bool SocketAddress::operator!=(const SocketAddress& other) const {
return !(*this == other);
}

template <typename T>
SocketAddressLRU<T>::SocketAddressLRU(
size_t max_size)
: max_size_(max_size) {}

template <typename T>
typename T::Type* SocketAddressLRU<T>::Peek(
const SocketAddress& address) const {
auto it = map_.find(address);
return it == std::end(map_) ? nullptr : &it->second->second;
}

template <typename T>
void SocketAddressLRU<T>::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 <typename T>
void SocketAddressLRU<T>::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>
typename T::Type* SocketAddressLRU<T>::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
Expand Down
36 changes: 36 additions & 0 deletions src/node_sockaddr.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "v8.h"

#include <string>
#include <list>
#include <unordered_map>

namespace node {
Expand Down Expand Up @@ -116,6 +117,41 @@ class SocketAddress : public MemoryRetainer {
sockaddr_storage address_;
};

template <typename T>
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<SocketAddress, Type>;
using Iterator = typename std::list<Pair>::iterator;

void CheckExpired();

std::list<Pair> list_;
SocketAddress::Map<Iterator> map_;
size_t max_size_;
};

} // namespace node

#endif // NOE_WANT_INTERNALS
Expand Down
70 changes: 70 additions & 0 deletions test/cctest/test_sockaddr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down Expand Up @@ -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<FooLRUTraits> 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<const sockaddr*>(&storage[0]));
SocketAddress addr2(reinterpret_cast<const sockaddr*>(&storage[1]));
SocketAddress addr3(reinterpret_cast<const sockaddr*>(&storage[2]));
SocketAddress addr4(reinterpret_cast<const sockaddr*>(&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));
}

0 comments on commit 6973a54

Please sign in to comment.