Skip to content

Commit 9acca7c

Browse files
committed
Merge branch 'master' into fix_default_node
2 parents 2388569 + f492f85 commit 9acca7c

12 files changed

+90
-10
lines changed

.github/workflows/integration.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ on:
1616
schedule:
1717
- cron: '0 1 * * *' # nightly build
1818

19+
permissions:
20+
contents: read # to fetch code (actions/checkout)
21+
1922
jobs:
2023

2124
dependency-audit:

.github/workflows/pypi-publish.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ on:
44
release:
55
types: [published]
66

7+
permissions:
8+
contents: read # to fetch code (actions/checkout)
9+
710
jobs:
811

912
build_and_package:

.github/workflows/release-drafter.yml

+5
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ on:
66
branches:
77
- master
88

9+
permissions: {}
910
jobs:
1011
update_release_draft:
12+
permissions:
13+
pull-requests: write # to add label to PR (release-drafter/release-drafter)
14+
contents: write # to create a github release (release-drafter/release-drafter)
15+
1116
runs-on: ubuntu-latest
1217
steps:
1318
# Drafts your next Release notes as Pull Requests are merged into "master"

.github/workflows/stale-issues.yml

+5
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ on:
33
schedule:
44
- cron: "0 0 * * *"
55

6+
permissions: {}
67
jobs:
78
stale:
9+
permissions:
10+
issues: write # to close stale issues (actions/stale)
11+
pull-requests: write # to close stale PRs (actions/stale)
12+
813
runs-on: ubuntu-latest
914
steps:
1015
- uses: actions/stale@v3

.readthedocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ version: 2
33
python:
44
install:
55
- requirements: ./docs/requirements.txt
6+
- requirements: requirements.txt
67

78
build:
89
os: ubuntu-20.04

CHANGES

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* Fixed "cannot pickle '_thread.lock' object" bug (#2354, #2297)
2929
* Added CredentialsProvider class to support password rotation
3030
* Enable Lock for asyncio cluster mode
31+
* Fix Sentinel.execute_command doesn't execute across the entire sentinel cluster bug (#2458)
3132
* Added a replacement for the default cluster node in the event of failure (#2463)
3233

3334
* 4.1.3 (Feb 8, 2022)

docs/backoff.rst

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _backoff-label:
2+
13
Backoff
24
#############
35

docs/examples/connection_examples.ipynb

-5
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,13 @@
116116
"user_connection.ping()"
117117
],
118118
"metadata": {}
119-
}
120119
},
121120
{
122121
"cell_type": "markdown",
123122
"source": [
124123
"## Connecting to a redis instance with standard credential provider"
125124
],
126125
"metadata": {}
127-
}
128126
},
129127
{
130128
"cell_type": "code",
@@ -162,15 +160,13 @@
162160
"user_connection.ping()"
163161
],
164162
"metadata": {}
165-
}
166163
},
167164
{
168165
"cell_type": "markdown",
169166
"source": [
170167
"## Connecting to a redis instance first with an initial credential set and then calling the credential provider"
171168
],
172169
"metadata": {}
173-
}
174170
},
175171
{
176172
"cell_type": "code",
@@ -200,7 +196,6 @@
200196
"cred_provider = InitCredsSetCredentialProvider(username=\"init_user\", password=\"init_pass\")"
201197
],
202198
"metadata": {}
203-
}
204199
},
205200
{
206201
"cell_type": "markdown",

docs/exceptions.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
.. _exceptions-label:
22

33
Exceptions
44
##########

docs/retry.rst

+66-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,69 @@ Retry Helpers
22
#############
33

44
.. automodule:: redis.retry
5-
:members:
5+
:members:
6+
7+
8+
Retry in Redis Standalone
9+
**************************
10+
11+
>>> from redis.backoff import ExponentialBackoff
12+
>>> from redis.retry import Retry
13+
>>> from redis.client import Redis
14+
>>> from redis.exceptions import (
15+
>>> BusyLoadingError,
16+
>>> ConnectionError,
17+
>>> TimeoutError
18+
>>> )
19+
>>>
20+
>>> # Run 3 retries with exponential backoff strategy
21+
>>> retry = Retry(ExponentialBackoff(), 3)
22+
>>> # Redis client with retries on custom errors
23+
>>> r = Redis(host='localhost', port=6379, retry=retry, retry_on_error=[BusyLoadingError, ConnectionError, TimeoutError])
24+
>>> # Redis client with retries on TimeoutError only
25+
>>> r_only_timeout = Redis(host='localhost', port=6379, retry=retry, retry_on_timeout=True)
26+
27+
As you can see from the example above, Redis client supports 3 parameters to configure the retry behaviour:
28+
29+
* ``retry``: :class:`~.Retry` instance with a :ref:`backoff-label` strategy and the max number of retries
30+
* ``retry_on_error``: list of :ref:`exceptions-label` to retry on
31+
* ``retry_on_timeout``: if ``True``, retry on :class:`~.TimeoutError` only
32+
33+
If either ``retry_on_error`` or ``retry_on_timeout`` are passed and no ``retry`` is given,
34+
by default it uses a ``Retry(NoBackoff(), 1)`` (meaning 1 retry right after the first failure).
35+
36+
37+
Retry in Redis Cluster
38+
**************************
39+
40+
>>> from redis.backoff import ExponentialBackoff
41+
>>> from redis.retry import Retry
42+
>>> from redis.cluster import RedisCluster
43+
>>>
44+
>>> # Run 3 retries with exponential backoff strategy
45+
>>> retry = Retry(ExponentialBackoff(), 3)
46+
>>> # Redis Cluster client with retries
47+
>>> rc = RedisCluster(host='localhost', port=6379, retry=retry, cluster_error_retry_attempts=2)
48+
49+
Retry behaviour in Redis Cluster is a little bit different from Standalone:
50+
51+
* ``retry``: :class:`~.Retry` instance with a :ref:`backoff-label` strategy and the max number of retries, default value is ``Retry(NoBackoff(), 0)``
52+
* ``cluster_error_retry_attempts``: number of times to retry before raising an error when :class:`~.TimeoutError` or :class:`~.ConnectionError` or :class:`~.ClusterDownError` are encountered, default value is ``3``
53+
54+
Let's consider the following example:
55+
56+
>>> from redis.backoff import ExponentialBackoff
57+
>>> from redis.retry import Retry
58+
>>> from redis.cluster import RedisCluster
59+
>>>
60+
>>> rc = RedisCluster(host='localhost', port=6379, retry=Retry(ExponentialBackoff(), 6), cluster_error_retry_attempts=1)
61+
>>> rc.set('foo', 'bar')
62+
63+
#. the client library calculates the hash slot for key 'foo'.
64+
#. given the hash slot, it then determines which node to connect to, in order to execute the command.
65+
#. during the connection, a :class:`~.ConnectionError` is raised.
66+
#. because we set ``retry=Retry(ExponentialBackoff(), 6)``, the client tries to reconnect to the node up to 6 times, with an exponential backoff between each attempt.
67+
#. even after 6 retries, the client is still unable to connect.
68+
#. because we set ``cluster_error_retry_attempts=1``, before giving up, the client starts a cluster update, removes the failed node from the startup nodes, and re-initializes the cluster.
69+
#. after the cluster has been re-initialized, it starts a new cycle of retries, up to 6 retries, with an exponential backoff.
70+
#. if the client can connect, we're good. Otherwise, the exception is finally raised to the caller, because we've run out of attempts.

redis/sentinel.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,10 @@ def execute_command(self, *args, **kwargs):
200200
kwargs.pop("once")
201201

202202
if once:
203+
random.choice(self.sentinels).execute_command(*args, **kwargs)
204+
else:
203205
for sentinel in self.sentinels:
204206
sentinel.execute_command(*args, **kwargs)
205-
else:
206-
random.choice(self.sentinels).execute_command(*args, **kwargs)
207207
return True
208208

209209
def __repr__(self):

tests/test_asyncio/test_cluster.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2613,7 +2613,7 @@ async def test_can_run_concurrent_pipelines(self, r: RedisCluster) -> None:
26132613
)
26142614

26152615
@pytest.mark.onlycluster
2616-
async def test_cluster_pipeline_with_default_node_error_command(self, r):
2616+
async def test_pipeline_with_default_node_error_command(self, r: RedisCluster):
26172617
"""
26182618
Test that the default node is being replaced when it raises a relevant exception
26192619
"""

0 commit comments

Comments
 (0)