Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 6973a54

Browse files
jasnelltargos
authored andcommittedAug 8, 2021
src: add SocketAddressLRU Utility
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]>
1 parent 6ca3489 commit 6973a54

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed
 

‎src/node_sockaddr-inl.h

+67
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "node_internals.h"
88
#include "node_sockaddr.h"
99
#include "util-inl.h"
10+
#include "memory_tracker-inl.h"
1011

1112
#include <string>
1213

@@ -164,6 +165,72 @@ bool SocketAddress::operator==(const SocketAddress& other) const {
164165
bool SocketAddress::operator!=(const SocketAddress& other) const {
165166
return !(*this == other);
166167
}
168+
169+
template <typename T>
170+
SocketAddressLRU<T>::SocketAddressLRU(
171+
size_t max_size)
172+
: max_size_(max_size) {}
173+
174+
template <typename T>
175+
typename T::Type* SocketAddressLRU<T>::Peek(
176+
const SocketAddress& address) const {
177+
auto it = map_.find(address);
178+
return it == std::end(map_) ? nullptr : &it->second->second;
179+
}
180+
181+
template <typename T>
182+
void SocketAddressLRU<T>::CheckExpired() {
183+
auto it = list_.rbegin();
184+
while (it != list_.rend()) {
185+
if (T::CheckExpired(it->first, it->second)) {
186+
map_.erase(it->first);
187+
list_.pop_back();
188+
it = list_.rbegin();
189+
continue;
190+
} else {
191+
break;
192+
}
193+
}
194+
}
195+
196+
template <typename T>
197+
void SocketAddressLRU<T>::MemoryInfo(MemoryTracker* tracker) const {
198+
tracker->TrackFieldWithSize("list", size() * sizeof(Pair));
199+
}
200+
201+
// If an item already exists for the given address, bump up it's
202+
// position in the LRU list and return it. If the item does not
203+
// exist, create it. If an item is created, check the size of the
204+
// cache and adjust if necessary. Whether the item exists or not,
205+
// purge expired items.
206+
template <typename T>
207+
typename T::Type* SocketAddressLRU<T>::Upsert(
208+
const SocketAddress& address) {
209+
210+
auto on_exit = OnScopeLeave([&]() { CheckExpired(); });
211+
212+
auto it = map_.find(address);
213+
if (it != std::end(map_)) {
214+
list_.splice(list_.begin(), list_, it->second);
215+
T::Touch(it->first, &it->second->second);
216+
return &it->second->second;
217+
}
218+
219+
list_.push_front(Pair(address, { }));
220+
map_[address] = list_.begin();
221+
T::Touch(list_.begin()->first, &list_.begin()->second);
222+
223+
// Drop the last item in the list if we are
224+
// over the size limit...
225+
if (map_.size() > max_size_) {
226+
auto last = list_.end();
227+
map_.erase((--last)->first);
228+
list_.pop_back();
229+
}
230+
231+
return &map_[address]->second;
232+
}
233+
167234
} // namespace node
168235

169236
#endif // NODE_WANT_INTERNALS

‎src/node_sockaddr.h

+36
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "v8.h"
1010

1111
#include <string>
12+
#include <list>
1213
#include <unordered_map>
1314

1415
namespace node {
@@ -116,6 +117,41 @@ class SocketAddress : public MemoryRetainer {
116117
sockaddr_storage address_;
117118
};
118119

120+
template <typename T>
121+
class SocketAddressLRU : public MemoryRetainer {
122+
public:
123+
using Type = typename T::Type;
124+
125+
inline explicit SocketAddressLRU(size_t max_size);
126+
127+
// If the item already exists, returns a reference to
128+
// the existing item, adjusting items position in the
129+
// LRU. If the item does not exist, emplaces the item
130+
// and returns the new item.
131+
Type* Upsert(const SocketAddress& address);
132+
133+
// Returns a reference to the item if it exists, or
134+
// nullptr. The position in the LRU is not modified.
135+
Type* Peek(const SocketAddress& address) const;
136+
137+
size_t size() const { return map_.size(); }
138+
size_t max_size() const { return max_size_; }
139+
140+
void MemoryInfo(MemoryTracker* tracker) const override;
141+
SET_MEMORY_INFO_NAME(SocketAddressLRU)
142+
SET_SELF_SIZE(SocketAddressLRU)
143+
144+
private:
145+
using Pair = std::pair<SocketAddress, Type>;
146+
using Iterator = typename std::list<Pair>::iterator;
147+
148+
void CheckExpired();
149+
150+
std::list<Pair> list_;
151+
SocketAddress::Map<Iterator> map_;
152+
size_t max_size_;
153+
};
154+
119155
} // namespace node
120156

121157
#endif // NOE_WANT_INTERNALS

‎test/cctest/test_sockaddr.cc

+70
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "gtest/gtest.h"
33

44
using node::SocketAddress;
5+
using node::SocketAddressLRU;
56

67
TEST(SocketAddress, SocketAddress) {
78
CHECK(SocketAddress::is_numeric_host("123.123.123.123"));
@@ -55,3 +56,72 @@ TEST(SocketAddress, SocketAddressIPv6) {
5556
addr.set_flow_label(12345);
5657
CHECK_EQ(addr.flow_label(), 12345);
5758
}
59+
60+
TEST(SocketAddressLRU, SocketAddressLRU) {
61+
struct Foo {
62+
int c;
63+
bool expired;
64+
};
65+
66+
struct FooLRUTraits {
67+
using Type = Foo;
68+
69+
static bool CheckExpired(const SocketAddress& address, const Type& type) {
70+
return type.expired;
71+
}
72+
73+
static void Touch(const SocketAddress& address, Type* type) {
74+
type->expired = false;
75+
}
76+
};
77+
78+
SocketAddressLRU<FooLRUTraits> lru(2);
79+
80+
sockaddr_storage storage[4];
81+
82+
SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage[0]);
83+
SocketAddress::ToSockAddr(AF_INET, "123.123.123.124", 443, &storage[1]);
84+
SocketAddress::ToSockAddr(AF_INET, "123.123.123.125", 443, &storage[2]);
85+
SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage[3]);
86+
87+
SocketAddress addr1(reinterpret_cast<const sockaddr*>(&storage[0]));
88+
SocketAddress addr2(reinterpret_cast<const sockaddr*>(&storage[1]));
89+
SocketAddress addr3(reinterpret_cast<const sockaddr*>(&storage[2]));
90+
SocketAddress addr4(reinterpret_cast<const sockaddr*>(&storage[3]));
91+
92+
Foo* foo = lru.Upsert(addr1);
93+
CHECK_NOT_NULL(foo);
94+
CHECK_EQ(foo->c, 0);
95+
CHECK_EQ(foo->expired, false);
96+
97+
foo->c = 1;
98+
foo->expired = true;
99+
100+
foo = lru.Upsert(addr1);
101+
CHECK_NOT_NULL(lru.Peek(addr1));
102+
CHECK_EQ(lru.Peek(addr1), lru.Peek(addr4));
103+
CHECK_EQ(lru.Peek(addr1)->c, 1);
104+
CHECK_EQ(lru.Peek(addr1)->expired, false);
105+
CHECK_EQ(lru.size(), 1);
106+
107+
foo = lru.Upsert(addr2);
108+
foo->c = 2;
109+
foo->expired = true;
110+
CHECK_NOT_NULL(lru.Peek(addr2));
111+
CHECK_EQ(lru.Peek(addr2)->c, 2);
112+
CHECK_EQ(lru.size(), 2);
113+
114+
foo->expired = true;
115+
116+
foo = lru.Upsert(addr3);
117+
foo->c = 3;
118+
foo->expired = false;
119+
CHECK_NOT_NULL(lru.Peek(addr3));
120+
CHECK_EQ(lru.Peek(addr3)->c, 3);
121+
CHECK_EQ(lru.size(), 1);
122+
123+
// addr1 was removed because we exceeded size.
124+
// addr2 was removed because it was expired.
125+
CHECK_NULL(lru.Peek(addr1));
126+
CHECK_NULL(lru.Peek(addr2));
127+
}

0 commit comments

Comments
 (0)
Please sign in to comment.