1
1
from itertools import chain
2
+ import copy
2
3
import datetime
3
4
import hashlib
4
5
import re
@@ -758,7 +759,13 @@ def __init__(self, host='localhost', port=6379,
758
759
ssl_cert_reqs = 'required' , ssl_ca_certs = None ,
759
760
ssl_check_hostname = False ,
760
761
max_connections = None , single_connection_client = False ,
761
- health_check_interval = 0 , client_name = None , username = None ):
762
+ health_check_interval = 0 , client_name = None , username = None ,
763
+ retry = None ):
764
+ """
765
+ Initialize a new Redis client.
766
+ To specify a retry policy, first set `retry_on_timeout` to `True`
767
+ then set `retry` to a valid `Retry` object
768
+ """
762
769
if not connection_pool :
763
770
if charset is not None :
764
771
warnings .warn (DeprecationWarning (
@@ -778,6 +785,7 @@ def __init__(self, host='localhost', port=6379,
778
785
'encoding_errors' : encoding_errors ,
779
786
'decode_responses' : decode_responses ,
780
787
'retry_on_timeout' : retry_on_timeout ,
788
+ 'retry' : copy .deepcopy (retry ),
781
789
'max_connections' : max_connections ,
782
790
'health_check_interval' : health_check_interval ,
783
791
'client_name' : client_name
@@ -940,21 +948,41 @@ def close(self):
940
948
self .connection = None
941
949
self .connection_pool .release (conn )
942
950
951
+ def _send_command_parse_response (self ,
952
+ conn ,
953
+ command_name ,
954
+ * args ,
955
+ ** options ):
956
+ """
957
+ Send a command and parse the response
958
+ """
959
+ conn .send_command (* args )
960
+ return self .parse_response (conn , command_name , ** options )
961
+
962
+ def _disconnect_raise (self , conn , error ):
963
+ """
964
+ Close the connection and raise an exception
965
+ if retry_on_timeout is not set or the error
966
+ is not a TimeoutError
967
+ """
968
+ conn .disconnect ()
969
+ if not (conn .retry_on_timeout and isinstance (error , TimeoutError )):
970
+ raise error
971
+
943
972
# COMMAND EXECUTION AND PROTOCOL PARSING
944
973
def execute_command (self , * args , ** options ):
945
974
"Execute a command and return a parsed response"
946
975
pool = self .connection_pool
947
976
command_name = args [0 ]
948
977
conn = self .connection or pool .get_connection (command_name , ** options )
978
+
949
979
try :
950
- conn .send_command (* args )
951
- return self .parse_response (conn , command_name , ** options )
952
- except (ConnectionError , TimeoutError ) as e :
953
- conn .disconnect ()
954
- if not (conn .retry_on_timeout and isinstance (e , TimeoutError )):
955
- raise
956
- conn .send_command (* args )
957
- return self .parse_response (conn , command_name , ** options )
980
+ return conn .retry .call_with_retry (
981
+ lambda : self ._send_command_parse_response (conn ,
982
+ command_name ,
983
+ * args ,
984
+ ** options ),
985
+ lambda error : self ._disconnect_raise (conn , error ))
958
986
finally :
959
987
if not self .connection :
960
988
pool .release (conn )
@@ -1142,24 +1170,31 @@ def execute_command(self, *args):
1142
1170
kwargs = {'check_health' : not self .subscribed }
1143
1171
self ._execute (connection , connection .send_command , * args , ** kwargs )
1144
1172
1145
- def _execute (self , connection , command , * args , ** kwargs ):
1146
- try :
1147
- return command (* args , ** kwargs )
1148
- except (ConnectionError , TimeoutError ) as e :
1149
- connection .disconnect ()
1150
- if not (connection .retry_on_timeout and
1151
- isinstance (e , TimeoutError )):
1152
- raise
1153
- # Connect manually here. If the Redis server is down, this will
1154
- # fail and raise a ConnectionError as desired.
1155
- connection .connect ()
1156
- # the ``on_connect`` callback should haven been called by the
1157
- # connection to resubscribe us to any channels and patterns we were
1158
- # previously listening to
1159
- return command (* args , ** kwargs )
1173
+ def _disconnect_raise_connect (self , conn , error ):
1174
+ """
1175
+ Close the connection and raise an exception
1176
+ if retry_on_timeout is not set or the error
1177
+ is not a TimeoutError. Otherwise, try to reconnect
1178
+ """
1179
+ conn .disconnect ()
1180
+ if not (conn .retry_on_timeout and isinstance (error , TimeoutError )):
1181
+ raise error
1182
+ conn .connect ()
1183
+
1184
+ def _execute (self , conn , command , * args , ** kwargs ):
1185
+ """
1186
+ Connect manually upon disconnection. If the Redis server is down,
1187
+ this will fail and raise a ConnectionError as desired.
1188
+ After reconnection, the ``on_connect`` callback should have been
1189
+ called by the # connection to resubscribe us to any channels and
1190
+ patterns we were previously listening to
1191
+ """
1192
+ return conn .retry .call_with_retry (
1193
+ lambda : command (* args , ** kwargs ),
1194
+ lambda error : self ._disconnect_raise_connect (conn , error ))
1160
1195
1161
1196
def parse_response (self , block = True , timeout = 0 ):
1162
- "Parse the response from a publish/subscribe command"
1197
+ """ Parse the response from a publish/subscribe command"" "
1163
1198
conn = self .connection
1164
1199
if conn is None :
1165
1200
raise RuntimeError (
@@ -1499,6 +1534,27 @@ def execute_command(self, *args, **kwargs):
1499
1534
return self .immediate_execute_command (* args , ** kwargs )
1500
1535
return self .pipeline_execute_command (* args , ** kwargs )
1501
1536
1537
+ def _disconnect_reset_raise (self , conn , error ):
1538
+ """
1539
+ Close the connection, reset watching state and
1540
+ raise an exception if we were watching,
1541
+ retry_on_timeout is not set,
1542
+ or the error is not a TimeoutError
1543
+ """
1544
+ conn .disconnect ()
1545
+ # if we were already watching a variable, the watch is no longer
1546
+ # valid since this connection has died. raise a WatchError, which
1547
+ # indicates the user should retry this transaction.
1548
+ if self .watching :
1549
+ self .reset ()
1550
+ raise WatchError ("A ConnectionError occurred on while "
1551
+ "watching one or more keys" )
1552
+ # if retry_on_timeout is not set, or the error is not
1553
+ # a TimeoutError, raise it
1554
+ if not (conn .retry_on_timeout and isinstance (error , TimeoutError )):
1555
+ self .reset ()
1556
+ raise
1557
+
1502
1558
def immediate_execute_command (self , * args , ** options ):
1503
1559
"""
1504
1560
Execute a command immediately, but don't auto-retry on a
@@ -1513,33 +1569,13 @@ def immediate_execute_command(self, *args, **options):
1513
1569
conn = self .connection_pool .get_connection (command_name ,
1514
1570
self .shard_hint )
1515
1571
self .connection = conn
1516
- try :
1517
- conn .send_command (* args )
1518
- return self .parse_response (conn , command_name , ** options )
1519
- except (ConnectionError , TimeoutError ) as e :
1520
- conn .disconnect ()
1521
- # if we were already watching a variable, the watch is no longer
1522
- # valid since this connection has died. raise a WatchError, which
1523
- # indicates the user should retry this transaction.
1524
- if self .watching :
1525
- self .reset ()
1526
- raise WatchError ("A ConnectionError occurred on while "
1527
- "watching one or more keys" )
1528
- # if retry_on_timeout is not set, or the error is not
1529
- # a TimeoutError, raise it
1530
- if not (conn .retry_on_timeout and isinstance (e , TimeoutError )):
1531
- self .reset ()
1532
- raise
1533
-
1534
- # retry_on_timeout is set, this is a TimeoutError and we are not
1535
- # already WATCHing any variables. retry the command.
1536
- try :
1537
- conn .send_command (* args )
1538
- return self .parse_response (conn , command_name , ** options )
1539
- except (ConnectionError , TimeoutError ):
1540
- # a subsequent failure should simply be raised
1541
- self .reset ()
1542
- raise
1572
+
1573
+ return conn .retry .call_with_retry (
1574
+ lambda : self ._send_command_parse_response (conn ,
1575
+ command_name ,
1576
+ * args ,
1577
+ ** options ),
1578
+ lambda error : self ._disconnect_reset_raise (conn , error ))
1543
1579
1544
1580
def pipeline_execute_command (self , * args , ** options ):
1545
1581
"""
@@ -1672,6 +1708,25 @@ def load_scripts(self):
1672
1708
if not exist :
1673
1709
s .sha = immediate ('SCRIPT LOAD' , s .script )
1674
1710
1711
+ def _disconnect_raise_reset (self , conn , error ):
1712
+ """
1713
+ Close the connection, raise an exception if we were watching,
1714
+ and raise an exception if retry_on_timeout is not set,
1715
+ or the error is not a TimeoutError
1716
+ """
1717
+ conn .disconnect ()
1718
+ # if we were watching a variable, the watch is no longer valid
1719
+ # since this connection has died. raise a WatchError, which
1720
+ # indicates the user should retry this transaction.
1721
+ if self .watching :
1722
+ raise WatchError ("A ConnectionError occurred on while "
1723
+ "watching one or more keys" )
1724
+ # if retry_on_timeout is not set, or the error is not
1725
+ # a TimeoutError, raise it
1726
+ if not (conn .retry_on_timeout and isinstance (error , TimeoutError )):
1727
+ self .reset ()
1728
+ raise
1729
+
1675
1730
def execute (self , raise_on_error = True ):
1676
1731
"Execute all the commands in the current pipeline"
1677
1732
stack = self .command_stack
@@ -1693,21 +1748,9 @@ def execute(self, raise_on_error=True):
1693
1748
self .connection = conn
1694
1749
1695
1750
try :
1696
- return execute (conn , stack , raise_on_error )
1697
- except (ConnectionError , TimeoutError ) as e :
1698
- conn .disconnect ()
1699
- # if we were watching a variable, the watch is no longer valid
1700
- # since this connection has died. raise a WatchError, which
1701
- # indicates the user should retry this transaction.
1702
- if self .watching :
1703
- raise WatchError ("A ConnectionError occurred on while "
1704
- "watching one or more keys" )
1705
- # if retry_on_timeout is not set, or the error is not
1706
- # a TimeoutError, raise it
1707
- if not (conn .retry_on_timeout and isinstance (e , TimeoutError )):
1708
- raise
1709
- # retry a TimeoutError when retry_on_timeout is set
1710
- return execute (conn , stack , raise_on_error )
1751
+ return conn .retry .call_with_retry (
1752
+ lambda : execute (conn , stack , raise_on_error ),
1753
+ lambda error : self ._disconnect_raise_reset (conn , error ))
1711
1754
finally :
1712
1755
self .reset ()
1713
1756
0 commit comments