Closed
Description
Version: redis-py 4.6.0, redis
Platform: Python 3.11.4 on Debian
Description:
Similar to a bug reported here #2563.
When maximum number of clients are used up for a Sentinel, there is an issue with the health check that causes a maximum recursion
Exception:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 778, in read_response
response = await self._parser.read_response(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 411, in read_response
await self.read_from_socket()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 392, in read_from_socket
buffer = await self._stream.read(self._read_size)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/asyncio/streams.py", line 690, in read
await self._wait_for_data('read')
File "/usr/local/lib/python3.11/asyncio/streams.py", line 520, in _wait_for_data
self._waiter = self._loop.create_future()
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "uvloop/loop.pyx", line 1412, in uvloop.loop.Loop.create_future
File "uvloop/loop.pyx", line 718, in uvloop.loop.Loop._new_future
File "/usr/local/lib/python3.11/traceback.py", line 231, in extract_stack
stack = StackSummary.extract(walk_stack(f), limit=limit)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/traceback.py", line 393, in extract
return klass._extract_from_extended_frame_gen(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/traceback.py", line 416, in _extract_from_extended_frame_gen
for f, (lineno, end_lineno, colno, end_colno) in frame_gen:
File "/usr/local/lib/python3.11/traceback.py", line 390, in extended_frame_gen
for f, lineno in frame_gen:
RecursionError: maximum recursion depth exceeded
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/plaid/src/plaid/plaid/web/endpoints/liveness.py", line 38, in dispatch
await super().dispatch()
File "/usr/local/lib/python3.11/site-packages/starlette/endpoints.py", line 42, in dispatch
response = await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/plaid/src/plaid/plaid/web/endpoints/liveness.py", line 72, in get
await self._check_redis()
File "/home/plaid/src/plaid/plaid/web/endpoints/liveness.py", line 55, in _check_redis
await redis_con.setex(
File "/home/plaid/src/plaid/plaid/core/data/redis_connector.py", line 85, in execute_command
return await super().execute_command(*args, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 513, in execute_command
conn = self.connection or await pool.get_connection(command_name, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1375, in get_connection
await connection.connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/sentinel.py", line 61, in connect
return await self.retry.call_with_retry(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/retry.py", line 59, in call_with_retry
return await do()
^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/sentinel.py", line 51, in _connect_retry
await self.connect_to(await self.connection_pool.get_master_address())
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/sentinel.py", line 41, in connect_to
await super().connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 592, in connect
await self.on_connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 659, in on_connect
await self.send_command("SELECT", self.db)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 752, in send_command
await self.send_packed_command(
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 715, in send_packed_command
await self.check_health()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 703, in check_health
await self.retry.call_with_retry(self._send_ping, self._ping_failed)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/retry.py", line 59, in call_with_retry
return await do()
^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 689, in _send_ping
await self.send_command("PING", check_health=False)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 752, in send_command
await self.send_packed_command(
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 713, in send_packed_command
await self.connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/sentinel.py", line 61, in connect
return await self.retry.call_with_retry(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/retry.py", line 59, in call_with_retry
return await do()
^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/sentinel.py", line 51, in _connect_retry
await self.connect_to(await self.connection_pool.get_master_address())
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/sentinel.py", line 41, in connect_to
await super().connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 592, in connect
await self.on_connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 659, in on_connect
await self.send_command("SELECT", self.db)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 752, in send_command
await self.send_packed_command(
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 715, in send_packed_command
await self.check_health()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 703, in check_health
await self.retry.call_with_retry(self._send_ping, self._ping_failed)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/retry.py", line 59, in call_with_retry
return await do()
^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 689, in _send_ping
await self.send_command("PING", check_health=False)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 752, in send_command
await self.send_packed_command(
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 713, in send_packed_command
await self.connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/sentinel.py", line 61, in connect
return await self.retry.call_with_retry(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/retry.py", line 59, in call_with_retry
return await do()
^^^^^^^^^^
<snip>
** Code to reproduce **
The health check is deliberately made short to generate the error
import asyncio
import traceback
import uuid
from redis.asyncio import Redis, Sentinel
def get_redis_conn():
sentinel_connection = Sentinel(
[('rfs-plaid', 26379)],
db=6,
health_check_interval=2,
retry_on_timeout=True,
)
return sentinel_connection.master_for(
'mymaster',
redis_class=Redis,
decode_responses=True
)
async def check_connection_limit_bug():
conn = get_redis_conn()
await conn.config_set('maxclients', 1000)
try:
await consume_client_connections()
while True:
await conn.get('test')
await asyncio.sleep(2)
finally:
await conn.config_set('maxclients', 10000)
pass
async def consume_client_connections():
for i in range(1010):
try:
conn = get_redis_conn()
unique_value = str(uuid.uuid4())
unique_key = f'liveness-check:{unique_value}'
await conn.setex(
name=unique_key,
time=60,
value=unique_value
)
await conn.getdel(
name=unique_key,
)
except:
print(traceback.format_exc())
asyncio.run(check_connection_limit_bug())