22
33require 'redis_client'
44require 'redis_client/cluster/errors'
5+ require 'redis_client/connection_mixin'
56require 'redis_client/middlewares'
67require 'redis_client/pooled'
78
89class RedisClient
910 class Cluster
1011 class Pipeline
12+ class Extended < ::RedisClient ::Pipeline
13+ attr_reader :outer_indices
14+
15+ def initialize ( command_builder )
16+ super
17+ @outer_indices = nil
18+ end
19+
20+ def add_outer_index ( index )
21+ @outer_indices ||= [ ]
22+ @outer_indices << index
23+ end
24+
25+ def get_inner_index ( outer_index )
26+ @outer_indices &.find_index ( outer_index )
27+ end
28+
29+ def get_callee_method ( inner_index )
30+ if @timeouts . is_a? ( Array ) && !@timeouts [ inner_index ] . nil?
31+ :blocking_call_v
32+ elsif _retryable?
33+ :call_once_v
34+ else
35+ :call_v
36+ end
37+ end
38+
39+ def get_command ( inner_index )
40+ @commands . is_a? ( Array ) ? @commands [ inner_index ] : nil
41+ end
42+
43+ def get_timeout ( inner_index )
44+ @timeouts . is_a? ( Array ) ? @timeouts [ inner_index ] : nil
45+ end
46+
47+ def get_block ( inner_index )
48+ @blocks . is_a? ( Array ) ? @blocks [ inner_index ] : nil
49+ end
50+ end
51+
52+ ::RedisClient ::ConnectionMixin . module_eval do
53+ def call_pipelined_aware_of_redirection ( commands , timeouts ) # rubocop:disable Metrics/AbcSize
54+ size = commands . size
55+ results = Array . new ( commands . size )
56+ @pending_reads += size
57+ write_multi ( commands )
58+
59+ redirection_indices = nil
60+ size . times do |index |
61+ timeout = timeouts && timeouts [ index ]
62+ result = read ( timeout )
63+ @pending_reads -= 1
64+ if result . is_a? ( CommandError )
65+ result . _set_command ( commands [ index ] )
66+ if result . message . start_with? ( 'MOVED' , 'ASK' )
67+ redirection_indices ||= [ ]
68+ redirection_indices << index
69+ end
70+ end
71+
72+ results [ index ] = result
73+ end
74+
75+ return results if redirection_indices . nil?
76+
77+ err = ::RedisClient ::Cluster ::Pipeline ::RedirectionNeeded . new
78+ err . replies = results
79+ err . indices = redirection_indices
80+ raise err
81+ end
82+ end
83+
1184 ReplySizeError = Class . new ( ::RedisClient ::Error )
85+
86+ class RedirectionNeeded < ::RedisClient ::Error
87+ attr_accessor :replies , :indices
88+ end
89+
1290 MAX_THREADS = Integer ( ENV . fetch ( 'REDIS_CLIENT_MAX_THREADS' , 5 ) )
1391
1492 def initialize ( router , command_builder , seed : Random . new_seed )
1593 @router = router
1694 @command_builder = command_builder
1795 @seed = seed
18- @pipelines = @indices = nil
96+ @pipelines = nil
1997 @size = 0
2098 end
2199
22100 def call ( *args , **kwargs , &block )
23101 command = @command_builder . generate ( args , kwargs )
24102 node_key = @router . find_node_key ( command , seed : @seed )
25- get_pipeline ( node_key ) . call_v ( command , &block )
26- index_pipeline ( node_key )
103+ append_pipeline ( node_key ) . call_v ( command , &block )
27104 end
28105
29106 def call_v ( args , &block )
30107 command = @command_builder . generate ( args )
31108 node_key = @router . find_node_key ( command , seed : @seed )
32- get_pipeline ( node_key ) . call_v ( command , &block )
33- index_pipeline ( node_key )
109+ append_pipeline ( node_key ) . call_v ( command , &block )
34110 end
35111
36112 def call_once ( *args , **kwargs , &block )
37113 command = @command_builder . generate ( args , kwargs )
38114 node_key = @router . find_node_key ( command , seed : @seed )
39- get_pipeline ( node_key ) . call_once_v ( command , &block )
40- index_pipeline ( node_key )
115+ append_pipeline ( node_key ) . call_once_v ( command , &block )
41116 end
42117
43118 def call_once_v ( args , &block )
44119 command = @command_builder . generate ( args )
45120 node_key = @router . find_node_key ( command , seed : @seed )
46- get_pipeline ( node_key ) . call_once_v ( command , &block )
47- index_pipeline ( node_key )
121+ append_pipeline ( node_key ) . call_once_v ( command , &block )
48122 end
49123
50124 def blocking_call ( timeout , *args , **kwargs , &block )
51125 command = @command_builder . generate ( args , kwargs )
52126 node_key = @router . find_node_key ( command , seed : @seed )
53- get_pipeline ( node_key ) . blocking_call_v ( timeout , command , &block )
54- index_pipeline ( node_key )
127+ append_pipeline ( node_key ) . blocking_call_v ( timeout , command , &block )
55128 end
56129
57130 def blocking_call_v ( timeout , args , &block )
58131 command = @command_builder . generate ( args )
59132 node_key = @router . find_node_key ( command , seed : @seed )
60- get_pipeline ( node_key ) . blocking_call_v ( timeout , command , &block )
61- index_pipeline ( node_key )
133+ append_pipeline ( node_key ) . blocking_call_v ( timeout , command , &block )
62134 end
63135
64136 def empty?
65137 @size . zero?
66138 end
67139
68- # TODO: https://github.com/redis-rb/redis-cluster-client/issues/37 handle redirections
69140 def execute # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
70141 all_replies = errors = nil
71142 @pipelines &.each_slice ( MAX_THREADS ) do |chuncked_pipelines |
@@ -77,40 +148,47 @@ def execute # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Met
77148 raise ReplySizeError , "commands: #{ pl . _size } , replies: #{ replies . size } " if pl . _size != replies . size
78149
79150 Thread . current . thread_variable_set ( :replies , replies )
151+ rescue ::RedisClient ::Cluster ::Pipeline ::RedirectionNeeded => e
152+ Thread . current . thread_variable_set ( :redirection_needed , e )
80153 rescue StandardError => e
81154 Thread . current . thread_variable_set ( :error , e )
82155 end
83156 end
84157
85158 threads . each do |t |
86159 t . join
160+
87161 if t . thread_variable? ( :replies )
88162 all_replies ||= Array . new ( @size )
89- @indices [ t . thread_variable_get ( :node_key ) ] . each_with_index { |gi , i | all_replies [ gi ] = t . thread_variable_get ( :replies ) [ i ] }
163+ @pipelines [ t . thread_variable_get ( :node_key ) ]
164+ . outer_indices
165+ . each_with_index { |outer , inner | all_replies [ outer ] = t . thread_variable_get ( :replies ) [ inner ] }
166+ elsif t . thread_variable? ( :redirection_needed )
167+ all_replies ||= Array . new ( @size )
168+ pipeline = @pipelines [ t . thread_variable_get ( :node_key ) ]
169+ err = t . thread_variable_get ( :redirection_needed )
170+ err . indices . each { |i | err . replies [ i ] = handle_redirection ( err . replies [ i ] , pipeline , i ) }
171+ pipeline . outer_indices . each_with_index { |outer , inner | all_replies [ outer ] = err . replies [ inner ] }
90172 elsif t . thread_variable? ( :error )
91173 errors ||= { }
92174 errors [ t . thread_variable_get ( :node_key ) ] = t . thread_variable_get ( :error )
93175 end
94176 end
95177 end
96178
97- return all_replies if errors . nil?
179+ raise :: RedisClient :: Cluster :: ErrorCollection , errors unless errors . nil?
98180
99- raise :: RedisClient :: Cluster :: ErrorCollection , errors
181+ all_replies
100182 end
101183
102184 private
103185
104- def get_pipeline ( node_key )
186+ def append_pipeline ( node_key )
105187 @pipelines ||= { }
106- @pipelines [ node_key ] ||= ::RedisClient ::Pipeline . new ( @command_builder )
107- end
108-
109- def index_pipeline ( node_key )
110- @indices ||= { }
111- @indices [ node_key ] ||= [ ]
112- @indices [ node_key ] << @size
188+ @pipelines [ node_key ] ||= ::RedisClient ::Cluster ::Pipeline ::Extended . new ( @command_builder )
189+ @pipelines [ node_key ] . add_outer_index ( @size )
113190 @size += 1
191+ @pipelines [ node_key ]
114192 end
115193
116194 def do_pipelining ( client , pipeline )
@@ -125,12 +203,52 @@ def send_pipeline(client, pipeline)
125203 results = client . send ( :ensure_connected , retryable : pipeline . _retryable? ) do |connection |
126204 commands = pipeline . _commands
127205 ::RedisClient ::Middlewares . call_pipelined ( commands , client . config ) do
128- connection . call_pipelined ( commands , pipeline . _timeouts )
206+ connection . call_pipelined_aware_of_redirection ( commands , pipeline . _timeouts )
129207 end
130208 end
131209
132210 pipeline . _coerce! ( results )
133211 end
212+
213+ def handle_redirection ( err , pipeline , inner_index )
214+ return err unless err . is_a? ( ::RedisClient ::CommandError )
215+
216+ if err . message . start_with? ( 'MOVED' )
217+ node = @router . assign_redirection_node ( err . message )
218+ try_redirection ( node , pipeline , inner_index )
219+ elsif err . message . start_with? ( 'ASK' )
220+ node = @router . assign_asking_node ( err . message )
221+ try_asking ( node ) ? try_redirection ( node , pipeline , inner_index ) : err
222+ else
223+ err
224+ end
225+ end
226+
227+ def try_redirection ( node , pipeline , inner_index )
228+ redirect_command ( node , pipeline , inner_index )
229+ rescue StandardError => e
230+ e
231+ end
232+
233+ def redirect_command ( node , pipeline , inner_index )
234+ method = pipeline . get_callee_method ( inner_index )
235+ command = pipeline . get_command ( inner_index )
236+ timeout = pipeline . get_timeout ( inner_index )
237+ block = pipeline . get_block ( inner_index )
238+ args = timeout . nil? ? [ ] : [ timeout ]
239+
240+ if block . nil?
241+ @router . try_send ( node , method , command , args )
242+ else
243+ @router . try_send ( node , method , command , args , &block )
244+ end
245+ end
246+
247+ def try_asking ( node )
248+ node . call ( 'ASKING' ) == 'OK'
249+ rescue StandardError
250+ false
251+ end
134252 end
135253 end
136254end
0 commit comments