@@ -1965,158 +1965,174 @@ def _send_cluster_commands(
1965
1965
# build a list of node objects based on node names we need to
1966
1966
nodes = {}
1967
1967
1968
- # as we move through each command that still needs to be processed,
1969
- # we figure out the slot number that command maps to, then from
1970
- # the slot determine the node.
1971
- for c in attempt :
1972
- while True :
1973
- # refer to our internal node -> slot table that
1974
- # tells us where a given command should route to.
1975
- # (it might be possible we have a cached node that no longer
1976
- # exists in the cluster, which is why we do this in a loop)
1977
- passed_targets = c .options .pop ("target_nodes" , None )
1978
- if passed_targets and not self ._is_nodes_flag (passed_targets ):
1979
- target_nodes = self ._parse_target_nodes (passed_targets )
1980
- else :
1981
- target_nodes = self ._determine_nodes (
1982
- * c .args , node_flag = passed_targets
1983
- )
1984
- if not target_nodes :
1968
+ try :
1969
+ # as we move through each command that still needs to be processed,
1970
+ # we figure out the slot number that command maps to, then from
1971
+ # the slot determine the node.
1972
+ for c in attempt :
1973
+ while True :
1974
+ # refer to our internal node -> slot table that
1975
+ # tells us where a given command should route to.
1976
+ # (it might be possible we have a cached node that no longer
1977
+ # exists in the cluster, which is why we do this in a loop)
1978
+ passed_targets = c .options .pop ("target_nodes" , None )
1979
+ if passed_targets and not self ._is_nodes_flag (passed_targets ):
1980
+ target_nodes = self ._parse_target_nodes (passed_targets )
1981
+ else :
1982
+ target_nodes = self ._determine_nodes (
1983
+ * c .args , node_flag = passed_targets
1984
+ )
1985
+ if not target_nodes :
1986
+ raise RedisClusterException (
1987
+ f"No targets were found to execute { c .args } command on"
1988
+ )
1989
+ if len (target_nodes ) > 1 :
1985
1990
raise RedisClusterException (
1986
- f"No targets were found to execute { c .args } command on "
1991
+ f"Too many targets for command { c .args } "
1987
1992
)
1988
- if len (target_nodes ) > 1 :
1989
- raise RedisClusterException (
1990
- f"Too many targets for command { c .args } "
1991
- )
1992
1993
1993
- node = target_nodes [0 ]
1994
- if node == self .get_default_node ():
1995
- is_default_node = True
1994
+ node = target_nodes [0 ]
1995
+ if node == self .get_default_node ():
1996
+ is_default_node = True
1996
1997
1997
- # now that we know the name of the node
1998
- # ( it's just a string in the form of host:port )
1999
- # we can build a list of commands for each node.
2000
- node_name = node .name
2001
- if node_name not in nodes :
2002
- redis_node = self .get_redis_connection (node )
1998
+ # now that we know the name of the node
1999
+ # ( it's just a string in the form of host:port )
2000
+ # we can build a list of commands for each node.
2001
+ node_name = node .name
2002
+ if node_name not in nodes :
2003
+ redis_node = self .get_redis_connection (node )
2004
+ try :
2005
+ connection = get_connection (redis_node , c .args )
2006
+ except (ConnectionError , TimeoutError ) as e :
2007
+ for n in nodes .values ():
2008
+ n .connection_pool .release (n .connection )
2009
+ n .connection = None
2010
+ nodes = {}
2011
+ if self .retry and isinstance (
2012
+ e , self .retry ._supported_errors
2013
+ ):
2014
+ backoff = self .retry ._backoff .compute (attempts_count )
2015
+ if backoff > 0 :
2016
+ time .sleep (backoff )
2017
+ self .nodes_manager .initialize ()
2018
+ if is_default_node :
2019
+ self .replace_default_node ()
2020
+ raise
2021
+ nodes [node_name ] = NodeCommands (
2022
+ redis_node .parse_response ,
2023
+ redis_node .connection_pool ,
2024
+ connection ,
2025
+ )
2026
+ nodes [node_name ].append (c )
2027
+ break
2028
+
2029
+ # send the commands in sequence.
2030
+ # we write to all the open sockets for each node first,
2031
+ # before reading anything
2032
+ # this allows us to flush all the requests out across the
2033
+ # network essentially in parallel
2034
+ # so that we can read them all in parallel as they come back.
2035
+ # we dont' multiplex on the sockets as they come available,
2036
+ # but that shouldn't make too much difference.
2037
+ node_commands = nodes .values ()
2038
+ for n in node_commands :
2039
+ n .write ()
2040
+
2041
+ for n in node_commands :
2042
+ n .read ()
2043
+
2044
+ # release all of the redis connections we allocated earlier
2045
+ # back into the connection pool.
2046
+ # we used to do this step as part of a try/finally block,
2047
+ # but it is really dangerous to
2048
+ # release connections back into the pool if for some
2049
+ # reason the socket has data still left in it
2050
+ # from a previous operation. The write and
2051
+ # read operations already have try/catch around them for
2052
+ # all known types of errors including connection
2053
+ # and socket level errors.
2054
+ # So if we hit an exception, something really bad
2055
+ # happened and putting any oF
2056
+ # these connections back into the pool is a very bad idea.
2057
+ # the socket might have unread buffer still sitting in it,
2058
+ # and then the next time we read from it we pass the
2059
+ # buffered result back from a previous command and
2060
+ # every single request after to that connection will always get
2061
+ # a mismatched result.
2062
+ for n in nodes .values ():
2063
+ n .connection_pool .release (n .connection )
2064
+ n .connection = None
2065
+ nodes = {}
2066
+
2067
+ # if the response isn't an exception it is a
2068
+ # valid response from the node
2069
+ # we're all done with that command, YAY!
2070
+ # if we have more commands to attempt, we've run into problems.
2071
+ # collect all the commands we are allowed to retry.
2072
+ # (MOVED, ASK, or connection errors or timeout errors)
2073
+ attempt = sorted (
2074
+ (
2075
+ c
2076
+ for c in attempt
2077
+ if isinstance (c .result , ClusterPipeline .ERRORS_ALLOW_RETRY )
2078
+ ),
2079
+ key = lambda x : x .position ,
2080
+ )
2081
+ if attempt and allow_redirections :
2082
+ # RETRY MAGIC HAPPENS HERE!
2083
+ # send these remaing commands one at a time using `execute_command`
2084
+ # in the main client. This keeps our retry logic
2085
+ # in one place mostly,
2086
+ # and allows us to be more confident in correctness of behavior.
2087
+ # at this point any speed gains from pipelining have been lost
2088
+ # anyway, so we might as well make the best
2089
+ # attempt to get the correct behavior.
2090
+ #
2091
+ # The client command will handle retries for each
2092
+ # individual command sequentially as we pass each
2093
+ # one into `execute_command`. Any exceptions
2094
+ # that bubble out should only appear once all
2095
+ # retries have been exhausted.
2096
+ #
2097
+ # If a lot of commands have failed, we'll be setting the
2098
+ # flag to rebuild the slots table from scratch.
2099
+ # So MOVED errors should correct themselves fairly quickly.
2100
+ self .reinitialize_counter += 1
2101
+ if self ._should_reinitialized ():
2102
+ self .nodes_manager .initialize ()
2103
+ if is_default_node :
2104
+ self .replace_default_node ()
2105
+ for c in attempt :
2003
2106
try :
2004
- connection = get_connection (redis_node , c .args )
2005
- except (ConnectionError , TimeoutError ) as e :
2006
- for n in nodes .values ():
2007
- n .connection_pool .release (n .connection )
2008
- if self .retry and isinstance (e , self .retry ._supported_errors ):
2009
- backoff = self .retry ._backoff .compute (attempts_count )
2010
- if backoff > 0 :
2011
- time .sleep (backoff )
2012
- self .nodes_manager .initialize ()
2013
- if is_default_node :
2014
- self .replace_default_node ()
2015
- raise
2016
- nodes [node_name ] = NodeCommands (
2017
- redis_node .parse_response ,
2018
- redis_node .connection_pool ,
2019
- connection ,
2020
- )
2021
- nodes [node_name ].append (c )
2022
- break
2023
-
2024
- # send the commands in sequence.
2025
- # we write to all the open sockets for each node first,
2026
- # before reading anything
2027
- # this allows us to flush all the requests out across the
2028
- # network essentially in parallel
2029
- # so that we can read them all in parallel as they come back.
2030
- # we dont' multiplex on the sockets as they come available,
2031
- # but that shouldn't make too much difference.
2032
- node_commands = nodes .values ()
2033
- for n in node_commands :
2034
- n .write ()
2035
-
2036
- for n in node_commands :
2037
- n .read ()
2038
-
2039
- # release all of the redis connections we allocated earlier
2040
- # back into the connection pool.
2041
- # we used to do this step as part of a try/finally block,
2042
- # but it is really dangerous to
2043
- # release connections back into the pool if for some
2044
- # reason the socket has data still left in it
2045
- # from a previous operation. The write and
2046
- # read operations already have try/catch around them for
2047
- # all known types of errors including connection
2048
- # and socket level errors.
2049
- # So if we hit an exception, something really bad
2050
- # happened and putting any oF
2051
- # these connections back into the pool is a very bad idea.
2052
- # the socket might have unread buffer still sitting in it,
2053
- # and then the next time we read from it we pass the
2054
- # buffered result back from a previous command and
2055
- # every single request after to that connection will always get
2056
- # a mismatched result.
2057
- for n in nodes .values ():
2058
- n .connection_pool .release (n .connection )
2059
-
2060
- # if the response isn't an exception it is a
2061
- # valid response from the node
2062
- # we're all done with that command, YAY!
2063
- # if we have more commands to attempt, we've run into problems.
2064
- # collect all the commands we are allowed to retry.
2065
- # (MOVED, ASK, or connection errors or timeout errors)
2066
- attempt = sorted (
2067
- (
2068
- c
2069
- for c in attempt
2070
- if isinstance (c .result , ClusterPipeline .ERRORS_ALLOW_RETRY )
2071
- ),
2072
- key = lambda x : x .position ,
2073
- )
2074
- if attempt and allow_redirections :
2075
- # RETRY MAGIC HAPPENS HERE!
2076
- # send these remaing commands one at a time using `execute_command`
2077
- # in the main client. This keeps our retry logic
2078
- # in one place mostly,
2079
- # and allows us to be more confident in correctness of behavior.
2080
- # at this point any speed gains from pipelining have been lost
2081
- # anyway, so we might as well make the best
2082
- # attempt to get the correct behavior.
2083
- #
2084
- # The client command will handle retries for each
2085
- # individual command sequentially as we pass each
2086
- # one into `execute_command`. Any exceptions
2087
- # that bubble out should only appear once all
2088
- # retries have been exhausted.
2089
- #
2090
- # If a lot of commands have failed, we'll be setting the
2091
- # flag to rebuild the slots table from scratch.
2092
- # So MOVED errors should correct themselves fairly quickly.
2093
- self .reinitialize_counter += 1
2094
- if self ._should_reinitialized ():
2095
- self .nodes_manager .initialize ()
2096
- if is_default_node :
2097
- self .replace_default_node ()
2098
- for c in attempt :
2099
- try :
2100
- # send each command individually like we
2101
- # do in the main client.
2102
- c .result = super ().execute_command (* c .args , ** c .options )
2103
- except RedisError as e :
2104
- c .result = e
2105
-
2106
- # turn the response back into a simple flat array that corresponds
2107
- # to the sequence of commands issued in the stack in pipeline.execute()
2108
- response = []
2109
- for c in sorted (stack , key = lambda x : x .position ):
2110
- if c .args [0 ] in self .cluster_response_callbacks :
2111
- c .result = self .cluster_response_callbacks [c .args [0 ]](
2112
- c .result , ** c .options
2113
- )
2114
- response .append (c .result )
2115
-
2116
- if raise_on_error :
2117
- self .raise_first_error (stack )
2107
+ # send each command individually like we
2108
+ # do in the main client.
2109
+ c .result = super ().execute_command (* c .args , ** c .options )
2110
+ except RedisError as e :
2111
+ c .result = e
2118
2112
2119
- return response
2113
+ # turn the response back into a simple flat array that corresponds
2114
+ # to the sequence of commands issued in the stack in pipeline.execute()
2115
+ response = []
2116
+ for c in sorted (stack , key = lambda x : x .position ):
2117
+ if c .args [0 ] in self .cluster_response_callbacks :
2118
+ c .result = self .cluster_response_callbacks [c .args [0 ]](
2119
+ c .result , ** c .options
2120
+ )
2121
+ response .append (c .result )
2122
+
2123
+ if raise_on_error :
2124
+ self .raise_first_error (stack )
2125
+
2126
+ return response
2127
+ except BaseException :
2128
+ # if nodes is not empty, a problem must have occurred
2129
+ # since we cant guarantee the state of the connections,
2130
+ # disconnect before returning it to the connection pool
2131
+ for n in nodes .values ():
2132
+ if n .connection :
2133
+ n .connection .disconnect ()
2134
+ n .connection_pool .release (n .connection )
2135
+ raise
2120
2136
2121
2137
def _fail_on_redirect (self , allow_redirections ):
2122
2138
""" """
0 commit comments