Skip to content

Commit ea24b5b

Browse files
Ensure command details are populated for Cluster clients
Previously, if a clustered client is unable to query any of the nodes for command details during setup, it'd initialize successfully, but without any command details. (This is a bit of a corner case - in order to hit this case, at least one node needs to be reachable when querying nodes + slots, but all nodes would need to be unreachable immediately afterwards when querying command details.) In this case, the client would be unable to determine which node a given command should be routed to. Because the client falls back to routing commands to a random node, and following redirects as necessary, this wouldn't cause any overt problems, but leaves the client following unnecessary redirects for the rest of the lifespan of the client. This patch changes the client to require that the initial fetch of command details succeeds (as is already done with slot and node detail fetching), and raises a CannotConnectError if that's not possible. Users should already handle this error, and retry creating the client later, or use some other suitable failure handling mechanism.
1 parent 688ac95 commit ea24b5b

File tree

2 files changed

+19
-9
lines changed

2 files changed

+19
-9
lines changed

lib/redis/cluster/command_loader.rb

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,21 @@ module CommandLoader
1010
module_function
1111

1212
def load(nodes)
13-
details = {}
14-
1513
nodes.each do |node|
16-
details = fetch_command_details(node)
17-
details.empty? ? next : break
14+
begin
15+
return fetch_command_details(node)
16+
rescue CannotConnectError, ConnectionError, CommandError
17+
next # can retry on another node
18+
end
1819
end
1920

20-
details
21+
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
2122
end
2223

2324
def fetch_command_details(node)
2425
node.call(%i[command]).map do |reply|
2526
[reply[0], { arity: reply[1], flags: reply[2], first: reply[3], last: reply[4], step: reply[5] }]
2627
end.to_h
27-
rescue CannotConnectError, ConnectionError, CommandError
28-
{} # can retry on another node
2928
end
3029

3130
private_class_method :fetch_command_details

test/cluster_client_key_hash_tags_test.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
class TestClusterClientKeyHashTags < Minitest::Test
77
include Helper::Cluster
88

9-
def build_described_class
10-
option = Redis::Cluster::Option.new(cluster: ['redis://127.0.0.1:7000'])
9+
def build_described_class(urls = ['redis://127.0.0.1:7000'])
10+
option = Redis::Cluster::Option.new(cluster: urls)
1111
node = Redis::Cluster::Node.new(option.per_node_key)
1212
details = Redis::Cluster::CommandLoader.load(node)
1313
Redis::Cluster::Command.new(details)
@@ -85,4 +85,15 @@ def test_whether_the_command_effect_is_readonly_or_not
8585
assert_equal false, described_class.should_send_to_slave?([:info])
8686
end
8787
end
88+
89+
def test_cannot_build_details_from_bad_urls
90+
assert_raises(Redis::CannotConnectError) do
91+
build_described_class(['redis://127.0.0.1:7006'])
92+
end
93+
end
94+
95+
def test_builds_details_from_a_mix_of_good_and_bad_urls
96+
described_class = build_described_class(['redis://127.0.0.1:7006', 'redis://127.0.0.1:7000'])
97+
assert_equal 'dogs:1', described_class.extract_first_key(%w[get dogs:1])
98+
end
8899
end

0 commit comments

Comments
 (0)