Skip to content

Commit f307cff

Browse files
committed
Merge branch 'master' of https://github.com/redis/redis-py into create-new-stream-commands
2 parents 477b9f9 + 4c9512b commit f307cff

19 files changed

+810
-90
lines changed

doctests/dt_time_series.py

Lines changed: 517 additions & 0 deletions
Large diffs are not rendered by default.

redis/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
DataError,
2121
InvalidPipelineStack,
2222
InvalidResponse,
23+
MaxConnectionsError,
2324
OutOfMemoryError,
2425
PubSubError,
2526
ReadOnlyError,
@@ -65,6 +66,7 @@ def int_or_str(value):
6566
"default_backoff",
6667
"InvalidPipelineStack",
6768
"InvalidResponse",
69+
"MaxConnectionsError",
6870
"OutOfMemoryError",
6971
"PubSubError",
7072
"ReadOnlyError",

redis/asyncio/cluster.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,13 @@ async def _execute_command(
814814
moved = False
815815

816816
return await target_node.execute_command(*args, **kwargs)
817-
except (BusyLoadingError, MaxConnectionsError):
817+
except BusyLoadingError:
818+
raise
819+
except MaxConnectionsError:
820+
# MaxConnectionsError indicates client-side resource exhaustion
821+
# (too many connections in the pool), not a node failure.
822+
# Don't treat this as a node failure - just re-raise the error
823+
# without reinitializing the cluster.
818824
raise
819825
except (ConnectionError, TimeoutError):
820826
# Connection retries are being handled in the node's

redis/asyncio/connection.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,13 +295,18 @@ async def connect(self):
295295
"""Connects to the Redis server if not already connected"""
296296
await self.connect_check_health(check_health=True)
297297

298-
async def connect_check_health(self, check_health: bool = True):
298+
async def connect_check_health(
299+
self, check_health: bool = True, retry_socket_connect: bool = True
300+
):
299301
if self.is_connected:
300302
return
301303
try:
302-
await self.retry.call_with_retry(
303-
lambda: self._connect(), lambda error: self.disconnect()
304-
)
304+
if retry_socket_connect:
305+
await self.retry.call_with_retry(
306+
lambda: self._connect(), lambda error: self.disconnect()
307+
)
308+
else:
309+
await self._connect()
305310
except asyncio.CancelledError:
306311
raise # in 3.7 and earlier, this is an Exception, not BaseException
307312
except (socket.timeout, asyncio.TimeoutError):
@@ -1037,6 +1042,7 @@ class ConnectionPool:
10371042
By default, TCP connections are created unless ``connection_class``
10381043
is specified. Use :py:class:`~redis.UnixDomainSocketConnection` for
10391044
unix sockets.
1045+
:py:class:`~redis.SSLConnection` can be used for SSL enabled connections.
10401046
10411047
Any additional keyword arguments are passed to the constructor of
10421048
``connection_class``.

redis/asyncio/retry.py

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Tuple, Type, TypeVar
33

44
from redis.exceptions import ConnectionError, RedisError, TimeoutError
5-
6-
if TYPE_CHECKING:
7-
from redis.backoff import AbstractBackoff
8-
5+
from redis.retry import AbstractRetry
96

107
T = TypeVar("T")
118

9+
if TYPE_CHECKING:
10+
from redis.backoff import AbstractBackoff
1211

13-
class Retry:
14-
"""Retry a specific number of times after a failure"""
1512

16-
__slots__ = "_backoff", "_retries", "_supported_errors"
13+
class Retry(AbstractRetry[RedisError]):
14+
__hash__ = AbstractRetry.__hash__
1715

1816
def __init__(
1917
self,
@@ -24,36 +22,17 @@ def __init__(
2422
TimeoutError,
2523
),
2624
):
27-
"""
28-
Initialize a `Retry` object with a `Backoff` object
29-
that retries a maximum of `retries` times.
30-
`retries` can be negative to retry forever.
31-
You can specify the types of supported errors which trigger
32-
a retry with the `supported_errors` parameter.
33-
"""
34-
self._backoff = backoff
35-
self._retries = retries
36-
self._supported_errors = supported_errors
25+
super().__init__(backoff, retries, supported_errors)
3726

38-
def update_supported_errors(self, specified_errors: list):
39-
"""
40-
Updates the supported errors with the specified error types
41-
"""
42-
self._supported_errors = tuple(
43-
set(self._supported_errors + tuple(specified_errors))
44-
)
45-
46-
def get_retries(self) -> int:
47-
"""
48-
Get the number of retries.
49-
"""
50-
return self._retries
27+
def __eq__(self, other: Any) -> bool:
28+
if not isinstance(other, Retry):
29+
return NotImplemented
5130

52-
def update_retries(self, value: int) -> None:
53-
"""
54-
Set the number of retries.
55-
"""
56-
self._retries = value
31+
return (
32+
self._backoff == other._backoff
33+
and self._retries == other._retries
34+
and set(self._supported_errors) == set(other._supported_errors)
35+
)
5736

5837
async def call_with_retry(
5938
self, do: Callable[[], Awaitable[T]], fail: Callable[[RedisError], Any]

redis/asyncio/sentinel.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@
1111
SSLConnection,
1212
)
1313
from redis.commands import AsyncSentinelCommands
14-
from redis.exceptions import ConnectionError, ReadOnlyError, ResponseError, TimeoutError
15-
from redis.utils import str_if_bytes
14+
from redis.exceptions import (
15+
ConnectionError,
16+
ReadOnlyError,
17+
ResponseError,
18+
TimeoutError,
19+
)
1620

1721

1822
class MasterNotFoundError(ConnectionError):
@@ -37,11 +41,10 @@ def __repr__(self):
3741

3842
async def connect_to(self, address):
3943
self.host, self.port = address
40-
await super().connect()
41-
if self.connection_pool.check_connection:
42-
await self.send_command("PING")
43-
if str_if_bytes(await self.read_response()) != "PONG":
44-
raise ConnectionError("PING failed")
44+
await self.connect_check_health(
45+
check_health=self.connection_pool.check_connection,
46+
retry_socket_connect=False,
47+
)
4548

4649
async def _connect_retry(self):
4750
if self._reader:

redis/backoff.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def __hash__(self) -> int:
170170
return hash((self._base, self._cap))
171171

172172
def __eq__(self, other) -> bool:
173-
if not isinstance(other, EqualJitterBackoff):
173+
if not isinstance(other, ExponentialWithJitterBackoff):
174174
return NotImplemented
175175

176176
return self._base == other._base and self._cap == other._cap

redis/cluster.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
DataError,
4040
ExecAbortError,
4141
InvalidPipelineStack,
42+
MaxConnectionsError,
4243
MovedError,
4344
RedisClusterException,
4445
RedisError,
@@ -1235,6 +1236,12 @@ def _execute_command(self, target_node, *args, **kwargs):
12351236
return response
12361237
except AuthenticationError:
12371238
raise
1239+
except MaxConnectionsError:
1240+
# MaxConnectionsError indicates client-side resource exhaustion
1241+
# (too many connections in the pool), not a node failure.
1242+
# Don't treat this as a node failure - just re-raise the error
1243+
# without reinitializing the cluster.
1244+
raise
12381245
except (ConnectionError, TimeoutError) as e:
12391246
# ConnectionError can also be raised if we couldn't get a
12401247
# connection from the pool before timing out, so check that

redis/connection.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
ChildDeadlockedError,
3232
ConnectionError,
3333
DataError,
34+
MaxConnectionsError,
3435
RedisError,
3536
ResponseError,
3637
TimeoutError,
@@ -378,13 +379,18 @@ def connect(self):
378379
"Connects to the Redis server if not already connected"
379380
self.connect_check_health(check_health=True)
380381

381-
def connect_check_health(self, check_health: bool = True):
382+
def connect_check_health(
383+
self, check_health: bool = True, retry_socket_connect: bool = True
384+
):
382385
if self._sock:
383386
return
384387
try:
385-
sock = self.retry.call_with_retry(
386-
lambda: self._connect(), lambda error: self.disconnect(error)
387-
)
388+
if retry_socket_connect:
389+
sock = self.retry.call_with_retry(
390+
lambda: self._connect(), lambda error: self.disconnect(error)
391+
)
392+
else:
393+
sock = self._connect()
388394
except socket.timeout:
389395
raise TimeoutError("Timeout connecting to server")
390396
except OSError as e:
@@ -1315,6 +1321,7 @@ class ConnectionPool:
13151321
By default, TCP connections are created unless ``connection_class``
13161322
is specified. Use class:`.UnixDomainSocketConnection` for
13171323
unix sockets.
1324+
:py:class:`~redis.SSLConnection` can be used for SSL enabled connections.
13181325
13191326
Any additional keyword arguments are passed to the constructor of
13201327
``connection_class``.
@@ -1556,7 +1563,7 @@ def get_encoder(self) -> Encoder:
15561563
def make_connection(self) -> "ConnectionInterface":
15571564
"Create a new connection"
15581565
if self._created_connections >= self.max_connections:
1559-
raise ConnectionError("Too many connections")
1566+
raise MaxConnectionsError("Too many connections")
15601567
self._created_connections += 1
15611568

15621569
if self.cache is not None:

redis/exceptions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,13 @@ class SlotNotCoveredError(RedisClusterException):
220220
pass
221221

222222

223-
class MaxConnectionsError(ConnectionError): ...
223+
class MaxConnectionsError(ConnectionError):
224+
"""
225+
Raised when a connection pool has reached its max_connections limit.
226+
This indicates pool exhaustion rather than an actual connection failure.
227+
"""
228+
229+
pass
224230

225231

226232
class CrossSlotTransactionError(RedisClusterException):

0 commit comments

Comments
 (0)