Skip to content

Commit 25fbabb

Browse files
Yu-Ho Hsiehmeta-codesync[bot]
authored andcommitted
Add Answer hash to prepare switch std::set to std::unordered_set or folly::FastSet
Summary: context is here https://fburl.com/workplace/3r5iucen inorder to transit from std::set to hash-based, we need the hasher. I separate the commit so its easier to rollback. Reviewed By: hanidamlaj Differential Revision: D91178557 fbshipit-source-id: dd1e182d884400c43048526ae7346f1694ee886d
1 parent 4bf23e7 commit 25fbabb

File tree

5 files changed

+244
-30
lines changed

5 files changed

+244
-30
lines changed

proxygen/lib/dns/CachingDNSResolver.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ void CachingDNSResolver::resolveHostname(DNSResolver::ResolutionCallback* cb,
2929

3030
if ((c_iter = cache_.find(name)) != cache_.end()) {
3131
CacheEntry& entry = c_iter->second;
32-
std::set<Answer>& res = entry.answers_;
32+
auto& res = entry.answers_;
3333
bool needQuery = false; // true if no or only expired answer exists
3434
bool hasCachedAnswer = false; // with respect to TTR
3535
bool isPartialMiss = false;
@@ -103,7 +103,7 @@ void CachingDNSResolver::insertCache(
103103
auto iter = cache.find(name);
104104
if (iter != cache.end()) {
105105
CacheEntry& entry = iter->second;
106-
std::set<Answer>& res = entry.answers_;
106+
auto& res = entry.answers_;
107107
for (auto& a : res) {
108108
if (entry.baseTime_ + a.ttl >= newEntry.baseTime_) {
109109
Answer ans(a);
@@ -150,7 +150,7 @@ void CachingDNSResolver::searchCache(std::string name,
150150
auto iter = cache.find(name);
151151
if (iter != cache.end()) {
152152
CacheEntry& entry = iter->second;
153-
std::set<Answer>& res = entry.answers_;
153+
auto& res = entry.answers_;
154154
for (auto& answer : res) {
155155
if (answer.type != Answer::AnswerType::AT_ADDRESS) {
156156
continue;

proxygen/lib/dns/CachingDNSResolver.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <vector>
1414

1515
#include <folly/container/EvictingCacheMap.h>
16+
#include <folly/container/F14Set.h>
1617
#include <proxygen/lib/utils/Time.h>
1718

1819
#include "proxygen/lib/dns/DNSResolver.h"
@@ -93,7 +94,7 @@ class CachingDNSResolver : public DNSResolver {
9394
// The answers are stored in a set to aid in removing duplicates when we
9495
// merge in answers from the old set. We explicitly shuffle the results
9596
// when we return them from the cache.
96-
std::set<Answer> answers_;
97+
folly::F14FastSet<Answer, AnswerHash> answers_;
9798
TimePoint baseTime_;
9899
};
99100

proxygen/lib/dns/DNSResolver.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ constexpr std::string_view kNoAddr{"No valid address or name found"};
1919
};
2020

2121
namespace proxygen {
22+
bool DNSResolver::Answer::operator==(const Answer& rhs) const noexcept {
23+
return type == rhs.type && address == rhs.address && name == rhs.name &&
24+
canonicalName == rhs.canonicalName && port == rhs.port &&
25+
priority == rhs.priority;
26+
}
27+
28+
size_t DNSResolver::AnswerHash::operator()(const Answer& a) const noexcept {
29+
// Hash by discriminant + relevant payload used in operator<
30+
return folly::hash::hash_combine(
31+
a.type, a.address.hash(), a.priority, a.name, a.canonicalName, a.port);
32+
}
2233

2334
// public static
2435
folly::exception_wrapper DNSResolver::makeNoNameException() noexcept {

proxygen/lib/dns/DNSResolver.h

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -191,35 +191,15 @@ class DNSResolver : public folly::DelayedDestruction {
191191
//default ctor
192192
Answer() = default;
193193

194-
// for unit tests
195-
bool operator==(const Answer& rhs) const {
196-
return !(operator<(rhs)) && !(rhs < (*this));
197-
}
194+
bool operator==(const Answer& rhs) const noexcept;
198195

199-
bool operator<(const Answer& rhs) const {
200-
if (type != rhs.type) {
201-
return type < rhs.type;
202-
}
196+
};
197+
198+
struct AnswerHash {
199+
size_t operator()(const Answer& a) const noexcept;
200+
};
203201

204-
if (type == AT_ADDRESS) {
205-
if (address != rhs.address) {
206-
return address < rhs.address;
207-
}
208-
} else if (type == AT_MX) {
209-
if (priority != rhs.priority) {
210-
return priority < rhs.priority;
211-
} else if (name != rhs.name) {
212-
return name < rhs.name;
213-
}
214-
} else { // AT_NAME or AT_CNAME
215-
if (name != rhs.name) {
216-
return name < rhs.name;
217-
}
218-
}
219202

220-
return false;
221-
}
222-
};
223203

224204
/**
225205
* Callback interface for resolution requests.
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include <folly/SocketAddress.h>
10+
#include <gtest/gtest.h>
11+
#include <proxygen/lib/dns/DNSResolver.h>
12+
13+
#include <unordered_set>
14+
15+
using folly::SocketAddress;
16+
using proxygen::DNSResolver;
17+
using std::chrono::seconds;
18+
19+
namespace {
20+
21+
size_t h(const DNSResolver::Answer& a) {
22+
return DNSResolver::AnswerHash{}(a);
23+
}
24+
25+
} // namespace
26+
27+
TEST(DNSResolverAnswerHashTest, EqualAnswersHaveEqualHash_Address) {
28+
// Same IP/port should be equal and have equal hash regardless of TTL
29+
DNSResolver::Answer a1(seconds(5), SocketAddress("127.0.0.1", 80));
30+
DNSResolver::Answer a2(seconds(30), SocketAddress("127.0.0.1", 80));
31+
32+
EXPECT_TRUE(a1 == a2);
33+
EXPECT_EQ(h(a1), h(a2));
34+
}
35+
36+
TEST(DNSResolverAnswerHashTest, EqualAnswersHaveEqualHash_Name) {
37+
DNSResolver::Answer n1(
38+
seconds(10), std::string("example.com"), DNSResolver::Answer::AT_NAME);
39+
DNSResolver::Answer n2(
40+
seconds(1), std::string("example.com"), DNSResolver::Answer::AT_NAME);
41+
42+
EXPECT_TRUE(n1 == n2);
43+
EXPECT_EQ(h(n1), h(n2));
44+
}
45+
46+
TEST(DNSResolverAnswerHashTest, EqualAnswersHaveEqualHash_CName) {
47+
DNSResolver::Answer c1(seconds(10),
48+
std::string("cname.example.com"),
49+
DNSResolver::Answer::AT_CNAME);
50+
DNSResolver::Answer c2(seconds(10),
51+
std::string("cname.example.com"),
52+
DNSResolver::Answer::AT_CNAME);
53+
EXPECT_TRUE(c1 == c2);
54+
EXPECT_EQ(h(c1), h(c2));
55+
}
56+
57+
TEST(DNSResolverAnswerHashTest, EqualAnswersHaveEqualHash_MX) {
58+
DNSResolver::Answer m1(
59+
seconds(60), /*priority*/ 10, std::string("mail.example.com"));
60+
DNSResolver::Answer m2(
61+
seconds(5), /*priority*/ 10, std::string("mail.example.com"));
62+
EXPECT_TRUE(m1 == m2);
63+
EXPECT_EQ(h(m1), h(m2));
64+
}
65+
66+
TEST(DNSResolverAnswerHashTest, DistinctAnswersBehaveInUnorderedSet) {
67+
// Build a set with distinct answers of different types and payloads
68+
std::unordered_set<DNSResolver::Answer, DNSResolver::AnswerHash> set;
69+
70+
DNSResolver::Answer a1(seconds(5), SocketAddress("127.0.0.1", 0));
71+
DNSResolver::Answer a2(seconds(5), SocketAddress("::1", 0));
72+
DNSResolver::Answer n1(
73+
seconds(5), std::string("example.com"), DNSResolver::Answer::AT_NAME);
74+
DNSResolver::Answer n2(
75+
seconds(5), std::string("www.example.com"), DNSResolver::Answer::AT_NAME);
76+
DNSResolver::Answer c1(seconds(5),
77+
std::string("cname.example.com"),
78+
DNSResolver::Answer::AT_CNAME);
79+
DNSResolver::Answer m1(
80+
seconds(5), 5 /*priority*/, std::string("mail.example.com"));
81+
DNSResolver::Answer m2(
82+
seconds(5), 10 /*priority*/, std::string("mail.example.com"));
83+
84+
EXPECT_TRUE(set.insert(a1).second);
85+
EXPECT_TRUE(set.insert(a2).second);
86+
EXPECT_TRUE(set.insert(n1).second);
87+
EXPECT_TRUE(set.insert(n2).second);
88+
EXPECT_TRUE(set.insert(c1).second);
89+
EXPECT_TRUE(set.insert(m1).second);
90+
EXPECT_TRUE(set.insert(m2).second);
91+
92+
// Reinserting equal values should not increase size
93+
EXPECT_FALSE(set.insert(DNSResolver::Answer(seconds(999),
94+
SocketAddress("127.0.0.1", 0)))
95+
.second);
96+
EXPECT_FALSE(set.insert(DNSResolver::Answer(seconds(1),
97+
std::string("example.com"),
98+
DNSResolver::Answer::AT_NAME))
99+
.second);
100+
EXPECT_FALSE(set.insert(DNSResolver::Answer(
101+
seconds(1), 5, std::string("mail.example.com")))
102+
.second);
103+
104+
EXPECT_EQ(set.size(), 7);
105+
}
106+
107+
TEST(DNSResolverAnswerHashTest, IgnoresTTLAndCreationTime) {
108+
// creationTime is set at construction time; we can't set it directly,
109+
// but we can construct the two objects at different times and ensure they
110+
// still compare equal and have equal hashes.
111+
DNSResolver::Answer a1(seconds(1), SocketAddress("1.2.3.4", 0));
112+
/* Small sleep to ensure creationTime differs if measured in seconds */
113+
DNSResolver::Answer a2(seconds(999), SocketAddress("1.2.3.4", 0));
114+
115+
EXPECT_TRUE(a1 == a2);
116+
EXPECT_EQ(h(a1), h(a2));
117+
}
118+
119+
// Tests to ensure each field in operator== and AnswerHash is properly checked.
120+
// If any field is missing from the implementation, these tests will fail.
121+
122+
TEST(DNSResolverAnswerHashTest, DifferentTypeNotEqual) {
123+
DNSResolver::Answer a1(
124+
seconds(5), std::string("example.com"), DNSResolver::Answer::AT_NAME);
125+
DNSResolver::Answer a2(
126+
seconds(5), std::string("example.com"), DNSResolver::Answer::AT_CNAME);
127+
128+
EXPECT_FALSE(a1 == a2);
129+
EXPECT_NE(h(a1), h(a2));
130+
}
131+
132+
TEST(DNSResolverAnswerHashTest, DifferentAddressNotEqual) {
133+
DNSResolver::Answer a1(seconds(5), SocketAddress("127.0.0.1", 80));
134+
DNSResolver::Answer a2(seconds(5), SocketAddress("127.0.0.2", 80));
135+
136+
EXPECT_FALSE(a1 == a2);
137+
EXPECT_NE(h(a1), h(a2));
138+
}
139+
140+
TEST(DNSResolverAnswerHashTest, DifferentNameNotEqual) {
141+
DNSResolver::Answer n1(
142+
seconds(5), std::string("example.com"), DNSResolver::Answer::AT_NAME);
143+
DNSResolver::Answer n2(
144+
seconds(5), std::string("www.example.com"), DNSResolver::Answer::AT_NAME);
145+
146+
EXPECT_FALSE(n1 == n2);
147+
EXPECT_NE(h(n1), h(n2));
148+
}
149+
150+
TEST(DNSResolverAnswerHashTest, DifferentCanonicalNameNotEqual) {
151+
DNSResolver::Answer a1(seconds(5), SocketAddress("127.0.0.1", 80));
152+
a1.canonicalName = "canonical1.example.com";
153+
154+
DNSResolver::Answer a2(seconds(5), SocketAddress("127.0.0.1", 80));
155+
a2.canonicalName = "canonical2.example.com";
156+
157+
EXPECT_FALSE(a1 == a2);
158+
EXPECT_NE(h(a1), h(a2));
159+
}
160+
161+
TEST(DNSResolverAnswerHashTest, DifferentPortNotEqual) {
162+
DNSResolver::Answer a1(seconds(5), SocketAddress("127.0.0.1", 0));
163+
a1.port = 8080;
164+
165+
DNSResolver::Answer a2(seconds(5), SocketAddress("127.0.0.1", 0));
166+
a2.port = 9090;
167+
168+
EXPECT_FALSE(a1 == a2);
169+
EXPECT_NE(h(a1), h(a2));
170+
}
171+
172+
TEST(DNSResolverAnswerHashTest, DifferentPriorityNotEqual) {
173+
DNSResolver::Answer m1(
174+
seconds(5), /*priority*/ 5, std::string("mail.example.com"));
175+
DNSResolver::Answer m2(
176+
seconds(5), /*priority*/ 10, std::string("mail.example.com"));
177+
178+
EXPECT_FALSE(m1 == m2);
179+
EXPECT_NE(h(m1), h(m2));
180+
}
181+
182+
TEST(DNSResolverAnswerHashTest, SameCanonicalNameAndPortEqual) {
183+
DNSResolver::Answer a1(seconds(5), SocketAddress("127.0.0.1", 80));
184+
a1.canonicalName = "canonical.example.com";
185+
a1.port = 8080;
186+
187+
DNSResolver::Answer a2(seconds(30), SocketAddress("127.0.0.1", 80));
188+
a2.canonicalName = "canonical.example.com";
189+
a2.port = 8080;
190+
191+
EXPECT_TRUE(a1 == a2);
192+
EXPECT_EQ(h(a1), h(a2));
193+
}
194+
195+
TEST(DNSResolverAnswerHashTest, CanonicalNameAndPortInUnorderedSet) {
196+
std::unordered_set<DNSResolver::Answer, DNSResolver::AnswerHash> set;
197+
198+
DNSResolver::Answer a1(seconds(5), SocketAddress("127.0.0.1", 80));
199+
a1.canonicalName = "canonical1.example.com";
200+
a1.port = 8080;
201+
202+
DNSResolver::Answer a2(seconds(5), SocketAddress("127.0.0.1", 80));
203+
a2.canonicalName = "canonical2.example.com";
204+
a2.port = 8080;
205+
206+
DNSResolver::Answer a3(seconds(5), SocketAddress("127.0.0.1", 80));
207+
a3.canonicalName = "canonical1.example.com";
208+
a3.port = 9090;
209+
210+
EXPECT_TRUE(set.insert(a1).second);
211+
EXPECT_TRUE(set.insert(a2).second);
212+
EXPECT_TRUE(set.insert(a3).second);
213+
EXPECT_EQ(set.size(), 3);
214+
215+
// Same canonicalName and port as a1 should not be inserted
216+
DNSResolver::Answer a4(seconds(999), SocketAddress("127.0.0.1", 80));
217+
a4.canonicalName = "canonical1.example.com";
218+
a4.port = 8080;
219+
220+
EXPECT_FALSE(set.insert(a4).second);
221+
EXPECT_EQ(set.size(), 3);
222+
}

0 commit comments

Comments
 (0)