Skip to content
14 changes: 12 additions & 2 deletions source/extensions/clusters/redis/redis_cluster_lb.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,18 @@ namespace Clusters {
namespace Redis {

bool ClusterSlot::operator==(const Envoy::Extensions::Clusters::Redis::ClusterSlot& rhs) const {
return start_ == rhs.start_ && end_ == rhs.end_ && primary_ == rhs.primary_ &&
replicas_ == rhs.replicas_;
if (start_ != rhs.start_ || end_ != rhs.end_ || *primary_ != *rhs.primary_ ||
replicas_.size() != rhs.replicas_.size()) {
return false;
}

for (auto const& replica : rhs.replicas_) {
if (replicas_.find(replica) == replicas_.end()) {
return false;
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this loop equivalent to just

return replicas_ == rhs.replicas_;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the test case, "replicas_ == rhs.replicas_" returns false, but In my code, it returns true. I'm trying to find the reason for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really not equal. Because in absl, "operator ==" uses "==" to compare the value, and "find" uses "eq_ref" to compare the value.
image
image


return true;
}

// RedisClusterLoadBalancerFactory
Expand Down
15 changes: 13 additions & 2 deletions source/extensions/clusters/redis/redis_cluster_lb.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,18 @@ class ClusterSlot {
int64_t start() const { return start_; }
int64_t end() const { return end_; }
Network::Address::InstanceConstSharedPtr primary() const { return primary_; }
const absl::flat_hash_set<Network::Address::InstanceConstSharedPtr>& replicas() const {
struct Hash {
size_t operator()(const Network::Address::InstanceConstSharedPtr& address) const {
return absl::Hash<std::string>()(address->asString());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a matter of good practice, you'd want to use return absl::Hash<absl::string_view>()(address->asStringView()); to avoid potentially creating a string temp while hashing. Though actually the name is held in a string in the main impl, so the asString() impl currently won't do that.

But in researching this I found a potential latent crash here. The doc for asString() says:

   * @return a human readable string for the address that represents the
   * physical/resolved address. (This will not necessarily include port
   * information, if applicable, since that may not be resolved until bind()).

This suggests to me that bind() might mutate the name. If that happens after we have already stored an address in a hash-table, I think this can crash in subsequent hash-table operations.

So my suggestion is to convert this to a map<string,Network::Address::InstanceConstSharedPtr>. This will result in string-copies for the map key, but will avoid crashing Envoy if bind() mutates the name. And if you do that you won't need this custom hasher/comparator.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

That's a good catch! There's no good in hashing keys which can mutate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this suggestion, I have use map instead of flat_hash_set and create a commit.

}
};
struct Eq {
bool operator()(const Network::Address::InstanceConstSharedPtr& lhs,
const Network::Address::InstanceConstSharedPtr& rhs) const {
return *lhs == *rhs;
}
};
const absl::flat_hash_set<Network::Address::InstanceConstSharedPtr, Hash, Eq>& replicas() const {
return replicas_;
}
void addReplica(Network::Address::InstanceConstSharedPtr replica_address) {
Expand All @@ -50,7 +61,7 @@ class ClusterSlot {
int64_t start_;
int64_t end_;
Network::Address::InstanceConstSharedPtr primary_;
absl::flat_hash_set<Network::Address::InstanceConstSharedPtr> replicas_;
absl::flat_hash_set<Network::Address::InstanceConstSharedPtr, Hash, Eq> replicas_;
};

using ClusterSlotsPtr = std::unique_ptr<std::vector<ClusterSlot>>;
Expand Down
20 changes: 17 additions & 3 deletions test/extensions/clusters/redis/redis_cluster_lb_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -349,18 +349,30 @@ TEST_F(RedisClusterLoadBalancerTest, ClusterSlotUpdate) {

TEST_F(RedisClusterLoadBalancerTest, ClusterSlotNoUpdate) {
Upstream::HostVector hosts{Upstream::makeTestHost(info_, "tcp://127.0.0.1:90", simTime()),
Upstream::makeTestHost(info_, "tcp://127.0.0.1:91", simTime()),
Upstream::makeTestHost(info_, "tcp://127.0.0.1:92", simTime()),
Upstream::makeTestHost(info_, "tcp://127.0.0.1:90", simTime()),
Upstream::makeTestHost(info_, "tcp://127.0.0.1:91", simTime()),
Upstream::makeTestHost(info_, "tcp://127.0.0.1:92", simTime())};
Upstream::HostVector replicas{Upstream::makeTestHost(info_, "tcp://127.0.0.2:90", simTime()),
Upstream::makeTestHost(info_, "tcp://127.0.0.2:91", simTime()),
Upstream::makeTestHost(info_, "tcp://127.0.0.2:90", simTime()),
Upstream::makeTestHost(info_, "tcp://127.0.0.2:91", simTime())};

ClusterSlotsPtr slots = std::make_unique<std::vector<ClusterSlot>>(std::vector<ClusterSlot>{
ClusterSlot(0, 1000, hosts[0]->address()),
ClusterSlot(1001, 2000, hosts[1]->address()),
ClusterSlot(2001, 16383, hosts[2]->address()),
});

(*slots)[0].addReplica(replicas[0]->address());
(*slots)[0].addReplica(replicas[1]->address());
Upstream::HostMap all_hosts{
{hosts[0]->address()->asString(), hosts[0]},
{hosts[1]->address()->asString(), hosts[1]},
{hosts[2]->address()->asString(), hosts[2]},
{replicas[0]->address()->asString(), replicas[0]},
{replicas[1]->address()->asString(), replicas[1]},
};

// A list of (hash: host_index) pair.
Expand All @@ -373,10 +385,12 @@ TEST_F(RedisClusterLoadBalancerTest, ClusterSlotNoUpdate) {

// Calling cluster slot update without change should not change assignment.
std::vector<ClusterSlot> updated_slot{
ClusterSlot(0, 1000, hosts[0]->address()),
ClusterSlot(1001, 2000, hosts[1]->address()),
ClusterSlot(2001, 16383, hosts[2]->address()),
ClusterSlot(0, 1000, hosts[3]->address()),
ClusterSlot(1001, 2000, hosts[4]->address()),
ClusterSlot(2001, 16383, hosts[5]->address()),
};
updated_slot[0].addReplica(replicas[3]->address());
updated_slot[0].addReplica(replicas[2]->address());
EXPECT_EQ(false, factory_->onClusterSlotUpdate(
std::make_unique<std::vector<ClusterSlot>>(updated_slot), all_hosts));
validateAssignment(hosts, expected_assignments);
Expand Down