Skip to content

Commit 5d49a47

Browse files
authored
fix: make the error identification middleware optional because of the use case (#344)
1 parent 8af34d0 commit 5d49a47

File tree

6 files changed

+84
-35
lines changed

6 files changed

+84
-35
lines changed

lib/redis_client/cluster/error_identification.rb

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ class RedisClient
44
class Cluster
55
module ErrorIdentification
66
def self.client_owns_error?(err, client)
7-
err.is_a?(TaggedError) && err.from?(client)
7+
return true unless identifiable?(err)
8+
9+
err.from?(client)
10+
end
11+
12+
def self.identifiable?(err)
13+
err.is_a?(TaggedError)
814
end
915

1016
module TaggedError

lib/redis_client/cluster/node.rb

+2-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
require 'redis_client'
44
require 'redis_client/config'
5-
require 'redis_client/cluster/error_identification'
65
require 'redis_client/cluster/errors'
76
require 'redis_client/cluster/node/primary_only'
87
require 'redis_client/cluster/node/random_replica'
@@ -79,11 +78,9 @@ def []=(index, element)
7978
end
8079

8180
class Config < ::RedisClient::Config
82-
def initialize(scale_read: false, middlewares: nil, **kwargs)
81+
def initialize(scale_read: false, **kwargs)
8382
@scale_read = scale_read
84-
middlewares ||= []
85-
middlewares.unshift ErrorIdentification::Middleware
86-
super(middlewares: middlewares, **kwargs)
83+
super(**kwargs)
8784
end
8885

8986
private
@@ -217,10 +214,6 @@ def reload!
217214
end
218215
end
219216

220-
def owns_error?(err)
221-
any? { |c| ErrorIdentification.client_owns_error?(err, c) }
222-
end
223-
224217
private
225218

226219
def make_topology_class(with_replica, replica_affinity)

lib/redis_client/cluster/router.rb

+6-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
require 'redis_client/cluster/normalized_cmd_name'
1111
require 'redis_client/cluster/transaction'
1212
require 'redis_client/cluster/optimistic_locking'
13+
require 'redis_client/cluster/error_identification'
1314

1415
class RedisClient
1516
class Cluster
@@ -68,7 +69,9 @@ def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSi
6869
raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
6970

7071
update_cluster_info! if e.errors.values.any? do |err|
71-
@node.owns_error?(err) && err.message.start_with?('CLUSTERDOWN Hash slot not served')
72+
next false if ::RedisClient::Cluster::ErrorIdentification.identifiable?(err) && @node.none? { |c| ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(err, c) }
73+
74+
err.message.start_with?('CLUSTERDOWN Hash slot not served')
7275
end
7376

7477
raise
@@ -97,7 +100,7 @@ def handle_redirection(node, retry_count:) # rubocop:disable Metrics/AbcSize, Me
97100
rescue ::RedisClient::CircuitBreaker::OpenCircuitError
98101
raise
99102
rescue ::RedisClient::CommandError => e
100-
raise unless ErrorIdentification.client_owns_error?(e, node)
103+
raise unless ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(e, node)
101104

102105
if e.message.start_with?('MOVED')
103106
node = assign_redirection_node(e.message)
@@ -117,7 +120,7 @@ def handle_redirection(node, retry_count:) # rubocop:disable Metrics/AbcSize, Me
117120
end
118121
raise
119122
rescue ::RedisClient::ConnectionError => e
120-
raise unless ErrorIdentification.client_owns_error?(e, node)
123+
raise unless ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(e, node)
121124

122125
update_cluster_info!
123126

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
module RedirectionEmulationMiddleware
4+
Setting = Struct.new(
5+
'RedirectionEmulationMiddlewareSetting',
6+
:slot, :to, :command, keyword_init: true
7+
)
8+
9+
def call(cmd, cfg)
10+
s = cfg.custom.fetch(:redirect)
11+
raise RedisClient::CommandError, "MOVED #{s.slot} #{s.to}" if cmd == s.command
12+
13+
super
14+
end
15+
16+
def call_pipelined(cmd, cfg)
17+
s = cfg.custom.fetch(:redirect)
18+
raise RedisClient::CommandError, "MOVED #{s.slot} #{s.to}" if cmd == s.command
19+
20+
super
21+
end
22+
end

test/redis_client/test_cluster.rb

+46-22
Original file line numberDiff line numberDiff line change
@@ -739,21 +739,45 @@ def test_circuit_breakers
739739
end
740740

741741
def test_only_reshards_own_errors
742-
@client.call_v(%w[SADD testkey testvalue1])
743-
@client.call_v(%w[SADD testkey testvalue2])
744742
slot = ::RedisClient::Cluster::KeySlotConverter.convert('testkey')
745743
router = @client.instance_variable_get(:@router)
746744
correct_primary_key = router.find_node_key_by_key('testkey', primary: true)
747745
broken_primary_key = (router.node_keys - [correct_primary_key]).first
746+
747+
client1 = new_test_client(
748+
middlewares: [
749+
::RedisClient::Cluster::ErrorIdentification::Middleware
750+
]
751+
)
752+
753+
client2 = new_test_client(
754+
middlewares: [
755+
::RedisClient::Cluster::ErrorIdentification::Middleware,
756+
RedirectionEmulationMiddleware
757+
],
758+
custom: {
759+
redirect: RedirectionEmulationMiddleware::Setting.new(
760+
slot: slot, to: broken_primary_key, command: %w[SET testkey client2]
761+
)
762+
}
763+
)
764+
748765
assert_raises(RedisClient::CommandError) do
749-
@client.sscan('testkey', retry_count: 0) do
750-
raise RedisClient::CommandError, "MOVED #{slot} #{broken_primary_key}"
766+
client1.call('SET', 'testkey', 'client1') do |got|
767+
assert_equal('OK', got)
768+
client2.call('SET', 'testkey', 'client2')
751769
end
752770
end
753771

754-
# The exception should not have causes @client to update its shard mappings, because it didn't
755-
# come from a RedisClient instance that @client knows about.
756-
assert_equal correct_primary_key, router.find_node_key_by_key('testkey', primary: true)
772+
# The exception should not have causes client1 to update its shard mappings, because it didn't
773+
# come from a RedisClient instance that client1 knows about.
774+
assert_equal(
775+
correct_primary_key,
776+
client1.instance_variable_get(:@router).find_node_key_by_key('testkey', primary: true)
777+
)
778+
779+
client1.close
780+
client2.close
757781
end
758782

759783
def test_pinning_single_key
@@ -832,12 +856,12 @@ def hiredis_used?
832856
class PrimaryOnly < TestingWrapper
833857
include Mixin
834858

835-
def new_test_client(capture_buffer: @captured_commands, **opts)
859+
def new_test_client(custom: { captured_commands: @captured_commands }, middlewares: [CommandCaptureMiddleware], **opts)
836860
config = ::RedisClient::ClusterConfig.new(
837861
nodes: TEST_NODE_URIS,
838862
fixed_hostname: TEST_FIXED_HOSTNAME,
839-
middlewares: [CommandCaptureMiddleware],
840-
custom: { captured_commands: capture_buffer },
863+
middlewares: middlewares,
864+
custom: custom,
841865
**TEST_GENERIC_OPTIONS,
842866
**opts
843867
)
@@ -848,14 +872,14 @@ def new_test_client(capture_buffer: @captured_commands, **opts)
848872
class ScaleReadRandom < TestingWrapper
849873
include Mixin
850874

851-
def new_test_client(capture_buffer: @captured_commands, **opts)
875+
def new_test_client(custom: { captured_commands: @captured_commands }, middlewares: [CommandCaptureMiddleware], **opts)
852876
config = ::RedisClient::ClusterConfig.new(
853877
nodes: TEST_NODE_URIS,
854878
replica: true,
855879
replica_affinity: :random,
856880
fixed_hostname: TEST_FIXED_HOSTNAME,
857-
middlewares: [CommandCaptureMiddleware],
858-
custom: { captured_commands: capture_buffer },
881+
middlewares: middlewares,
882+
custom: custom,
859883
**TEST_GENERIC_OPTIONS,
860884
**opts
861885
)
@@ -866,14 +890,14 @@ def new_test_client(capture_buffer: @captured_commands, **opts)
866890
class ScaleReadRandomWithPrimary < TestingWrapper
867891
include Mixin
868892

869-
def new_test_client(capture_buffer: @captured_commands, **opts)
893+
def new_test_client(custom: { captured_commands: @captured_commands }, middlewares: [CommandCaptureMiddleware], **opts)
870894
config = ::RedisClient::ClusterConfig.new(
871895
nodes: TEST_NODE_URIS,
872896
replica: true,
873897
replica_affinity: :random_with_primary,
874898
fixed_hostname: TEST_FIXED_HOSTNAME,
875-
middlewares: [CommandCaptureMiddleware],
876-
custom: { captured_commands: capture_buffer },
899+
middlewares: middlewares,
900+
custom: custom,
877901
**TEST_GENERIC_OPTIONS,
878902
**opts
879903
)
@@ -884,14 +908,14 @@ def new_test_client(capture_buffer: @captured_commands, **opts)
884908
class ScaleReadLatency < TestingWrapper
885909
include Mixin
886910

887-
def new_test_client(capture_buffer: @captured_commands, **opts)
911+
def new_test_client(custom: { captured_commands: @captured_commands }, middlewares: [CommandCaptureMiddleware], **opts)
888912
config = ::RedisClient::ClusterConfig.new(
889913
nodes: TEST_NODE_URIS,
890914
replica: true,
891915
replica_affinity: :latency,
892916
fixed_hostname: TEST_FIXED_HOSTNAME,
893-
middlewares: [CommandCaptureMiddleware],
894-
custom: { captured_commands: capture_buffer },
917+
middlewares: middlewares,
918+
custom: custom,
895919
**TEST_GENERIC_OPTIONS,
896920
**opts
897921
)
@@ -902,12 +926,12 @@ def new_test_client(capture_buffer: @captured_commands, **opts)
902926
class Pooled < TestingWrapper
903927
include Mixin
904928

905-
def new_test_client(capture_buffer: @captured_commands, **opts)
929+
def new_test_client(custom: { captured_commands: @captured_commands }, middlewares: [CommandCaptureMiddleware], **opts)
906930
config = ::RedisClient::ClusterConfig.new(
907931
nodes: TEST_NODE_URIS,
908932
fixed_hostname: TEST_FIXED_HOSTNAME,
909-
middlewares: [CommandCaptureMiddleware],
910-
custom: { captured_commands: capture_buffer },
933+
middlewares: middlewares,
934+
custom: custom,
911935
**TEST_GENERIC_OPTIONS,
912936
**opts
913937
)

test/testing_helper.rb

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require 'testing_constants'
88
require 'cluster_controller'
99
require 'command_capture_middleware'
10+
require 'redirection_emulation_middleware'
1011

1112
case ENV.fetch('REDIS_CONNECTION_DRIVER', 'ruby')
1213
when 'hiredis' then require 'hiredis-client'

0 commit comments

Comments
 (0)