29
29
from redis .asyncio .lock import Lock
30
30
from redis .asyncio .retry import Retry
31
31
from redis .auth .token import TokenInterface
32
- from redis .backoff import default_backoff
32
+ from redis .backoff import ExponentialWithJitterBackoff , NoBackoff
33
33
from redis .client import EMPTY_RESPONSE , NEVER_DECODE , AbstractRedis
34
34
from redis .cluster import (
35
35
PIPELINE_BLOCKED_COMMANDS ,
@@ -143,19 +143,23 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
143
143
To avoid reinitializing the cluster on moved errors, set reinitialize_steps to
144
144
0.
145
145
:param cluster_error_retry_attempts:
146
- | Number of times to retry before raising an error when :class:`~.TimeoutError`
147
- or :class:`~.ConnectionError` or :class:`~.ClusterDownError` are encountered
148
- :param connection_error_retry_attempts:
149
- | Number of times to retry before reinitializing when :class:`~.TimeoutError`
150
- or :class:`~.ConnectionError` are encountered.
151
- The default backoff strategy will be set if Retry object is not passed (see
152
- default_backoff in backoff.py). To change it, pass a custom Retry object
153
- using the "retry" keyword.
146
+ | @deprecated - Please configure the 'retry' object instead
147
+ In case 'retry' object is set - this argument is ignored!
148
+
149
+ Number of times to retry before raising an error when :class:`~.TimeoutError`,
150
+ :class:`~.ConnectionError`, :class:`~.SlotNotCoveredError`
151
+ or :class:`~.ClusterDownError` are encountered
152
+ :param retry:
153
+ | A retry object that defines the retry strategy and the number of
154
+ retries for the cluster client.
155
+ In current implementation for the cluster client (starting form redis-py version 6.0.0)
156
+ the retry object is not yet fully utilized, instead it is used just to determine
157
+ the number of retries for the cluster client.
158
+ In the future releases the retry object will be used to handle the cluster client retries!
154
159
:param max_connections:
155
160
| Maximum number of connections per node. If there are no free connections & the
156
161
maximum number of connections are already created, a
157
- :class:`~.MaxConnectionsError` is raised. This error may be retried as defined
158
- by :attr:`connection_error_retry_attempts`
162
+ :class:`~.MaxConnectionsError` is raised.
159
163
:param address_remap:
160
164
| An optional callable which, when provided with an internal network
161
165
address of a node, e.g. a `(host, port)` tuple, will return the address
@@ -211,10 +215,9 @@ def from_url(cls, url: str, **kwargs: Any) -> "RedisCluster":
211
215
__slots__ = (
212
216
"_initialize" ,
213
217
"_lock" ,
214
- "cluster_error_retry_attempts " ,
218
+ "retry " ,
215
219
"command_flags" ,
216
220
"commands_parser" ,
217
- "connection_error_retry_attempts" ,
218
221
"connection_kwargs" ,
219
222
"encoder" ,
220
223
"node_flags" ,
@@ -231,6 +234,13 @@ def from_url(cls, url: str, **kwargs: Any) -> "RedisCluster":
231
234
reason = "Please configure the 'load_balancing_strategy' instead" ,
232
235
version = "5.0.3" ,
233
236
)
237
+ @deprecated_args (
238
+ args_to_warn = [
239
+ "cluster_error_retry_attempts" ,
240
+ ],
241
+ reason = "Please configure the 'retry' object instead" ,
242
+ version = "6.0.0" ,
243
+ )
234
244
def __init__ (
235
245
self ,
236
246
host : Optional [str ] = None ,
@@ -242,8 +252,9 @@ def __init__(
242
252
load_balancing_strategy : Optional [LoadBalancingStrategy ] = None ,
243
253
reinitialize_steps : int = 5 ,
244
254
cluster_error_retry_attempts : int = 3 ,
245
- connection_error_retry_attempts : int = 3 ,
246
255
max_connections : int = 2 ** 31 ,
256
+ retry : Optional ["Retry" ] = None ,
257
+ retry_on_error : Optional [List [Type [Exception ]]] = None ,
247
258
# Client related kwargs
248
259
db : Union [str , int ] = 0 ,
249
260
path : Optional [str ] = None ,
@@ -263,8 +274,6 @@ def __init__(
263
274
socket_keepalive : bool = False ,
264
275
socket_keepalive_options : Optional [Mapping [int , Union [int , bytes ]]] = None ,
265
276
socket_timeout : Optional [float ] = None ,
266
- retry : Optional ["Retry" ] = None ,
267
- retry_on_error : Optional [List [Type [Exception ]]] = None ,
268
277
# SSL related kwargs
269
278
ssl : bool = False ,
270
279
ssl_ca_certs : Optional [str ] = None ,
@@ -318,7 +327,6 @@ def __init__(
318
327
"socket_keepalive" : socket_keepalive ,
319
328
"socket_keepalive_options" : socket_keepalive_options ,
320
329
"socket_timeout" : socket_timeout ,
321
- "retry" : retry ,
322
330
"protocol" : protocol ,
323
331
}
324
332
@@ -342,17 +350,15 @@ def __init__(
342
350
# Call our on_connect function to configure READONLY mode
343
351
kwargs ["redis_connect_func" ] = self .on_connect
344
352
345
- self .retry = retry
346
- if retry or retry_on_error or connection_error_retry_attempts > 0 :
347
- # Set a retry object for all cluster nodes
348
- self .retry = retry or Retry (
349
- default_backoff (), connection_error_retry_attempts
353
+ if retry :
354
+ self .retry = retry
355
+ else :
356
+ self .retry = Retry (
357
+ backoff = ExponentialWithJitterBackoff (base = 1 , cap = 10 ),
358
+ retries = cluster_error_retry_attempts ,
350
359
)
351
- if not retry_on_error :
352
- # Default errors for retrying
353
- retry_on_error = [ConnectionError , TimeoutError ]
360
+ if retry_on_error :
354
361
self .retry .update_supported_errors (retry_on_error )
355
- kwargs .update ({"retry" : self .retry })
356
362
357
363
kwargs ["response_callbacks" ] = _RedisCallbacks .copy ()
358
364
if kwargs .get ("protocol" ) in ["3" , 3 ]:
@@ -389,8 +395,6 @@ def __init__(
389
395
self .read_from_replicas = read_from_replicas
390
396
self .load_balancing_strategy = load_balancing_strategy
391
397
self .reinitialize_steps = reinitialize_steps
392
- self .cluster_error_retry_attempts = cluster_error_retry_attempts
393
- self .connection_error_retry_attempts = connection_error_retry_attempts
394
398
self .reinitialize_counter = 0
395
399
self .commands_parser = AsyncCommandsParser ()
396
400
self .node_flags = self .__class__ .NODE_FLAGS .copy ()
@@ -561,15 +565,13 @@ def get_connection_kwargs(self) -> Dict[str, Optional[Any]]:
561
565
"""Get the kwargs passed to :class:`~redis.asyncio.connection.Connection`."""
562
566
return self .connection_kwargs
563
567
564
- def get_retry (self ) -> Optional [ " Retry" ] :
568
+ def get_retry (self ) -> Retry :
565
569
return self .retry
566
570
567
- def set_retry (self , retry : "Retry" ) -> None :
571
+ def set_retry (self , retry : Retry ) -> None :
572
+ if not isinstance (retry , Retry ):
573
+ raise TypeError ("retry must be a valid instance of redis.retry.Retry" )
568
574
self .retry = retry
569
- for node in self .get_nodes ():
570
- node .connection_kwargs .update ({"retry" : retry })
571
- for conn in node ._connections :
572
- conn .retry = retry
573
575
574
576
def set_response_callback (self , command : str , callback : ResponseCallbackT ) -> None :
575
577
"""Set a custom response callback."""
@@ -688,8 +690,8 @@ async def execute_command(self, *args: EncodableT, **kwargs: Any) -> Any:
688
690
"""
689
691
Execute a raw command on the appropriate cluster node or target_nodes.
690
692
691
- It will retry the command as specified by :attr:`cluster_error_retry_attempts` &
692
- then raise an exception.
693
+ It will retry the command as specified by the retries property of
694
+ the :attr:`retry` & then raise an exception.
693
695
694
696
:param args:
695
697
| Raw command args
@@ -705,7 +707,7 @@ async def execute_command(self, *args: EncodableT, **kwargs: Any) -> Any:
705
707
command = args [0 ]
706
708
target_nodes = []
707
709
target_nodes_specified = False
708
- retry_attempts = self .cluster_error_retry_attempts
710
+ retry_attempts = self .retry . get_retries ()
709
711
710
712
passed_targets = kwargs .pop ("target_nodes" , None )
711
713
if passed_targets and not self ._is_node_flag (passed_targets ):
@@ -1042,7 +1044,23 @@ def acquire_connection(self) -> Connection:
1042
1044
return self ._free .popleft ()
1043
1045
except IndexError :
1044
1046
if len (self ._connections ) < self .max_connections :
1045
- connection = self .connection_class (** self .connection_kwargs )
1047
+ # We are configuring the connection pool not to retry
1048
+ # connections on lower level clients to avoid retrying
1049
+ # connections to nodes that are not reachable
1050
+ # and to avoid blocking the connection pool.
1051
+ # The only error that will have some handling in the lower
1052
+ # level clients is ConnectionError which will trigger disconnection
1053
+ # of the socket.
1054
+ # The retries will be handled on cluster client level
1055
+ # where we will have proper handling of the cluster topology
1056
+ retry = Retry (
1057
+ backoff = NoBackoff (),
1058
+ retries = 0 ,
1059
+ supported_errors = (ConnectionError ,),
1060
+ )
1061
+ connection_kwargs = self .connection_kwargs .copy ()
1062
+ connection_kwargs ["retry" ] = retry
1063
+ connection = self .connection_class (** connection_kwargs )
1046
1064
self ._connections .append (connection )
1047
1065
return connection
1048
1066
@@ -1538,7 +1556,7 @@ async def execute(
1538
1556
"""
1539
1557
Execute the pipeline.
1540
1558
1541
- It will retry the commands as specified by :attr:`cluster_error_retry_attempts `
1559
+ It will retry the commands as specified by retries specified in :attr:`retry `
1542
1560
& then raise an exception.
1543
1561
1544
1562
:param raise_on_error:
@@ -1554,7 +1572,7 @@ async def execute(
1554
1572
return []
1555
1573
1556
1574
try :
1557
- retry_attempts = self ._client .cluster_error_retry_attempts
1575
+ retry_attempts = self ._client .retry . get_retries ()
1558
1576
while True :
1559
1577
try :
1560
1578
if self ._client ._initialize :
0 commit comments