redis-py: "No connection available" errors since 5.0.1
Version: 5.0.1
Platform: Python 3.11.5(3.11.5 (main, Sep 4 2023, 15:30:52) [GCC 10.2.1 20210110])
Description:
Since 5.0.1, we noticed a significant increase in No connection available. errors from redis. We are using cashews (6.3.0) + redis-py for request caching for our fastapi application. We don’t experience this with 5.0.0.
Stack trace:
CancelledError: null
File "redis/asyncio/connection.py", line 1170, in get_connection
async with self._condition:
File "asyncio/locks.py", line 15, in __aenter__
await self.acquire()
File "asyncio/locks.py", line 114, in acquire
await fut
TimeoutError: null
File "redis/asyncio/connection.py", line 1169, in get_connection
async with async_timeout(self.timeout):
File "asyncio/timeouts.py", line 111, in __aexit__
raise TimeoutError from exc_val
ConnectionError: No connection available.
File "cashews/backends/redis/client.py", line 26, in execute_command
return await super().execute_command(command, *args, **kwargs)
File "redis/asyncio/client.py", line 601, in execute_command
conn = self.connection or await pool.get_connection(command_name, **options)
File "redis/asyncio/connection.py", line 1174, in get_connection
raise ConnectionError("No connection available.") from err
About this issue
- Original URL
- State: open
- Created 9 months ago
- Reactions: 8
- Comments: 30 (14 by maintainers)
Thank you for your solution. The second solution is a method that I feel is more understandable, and it indeed reduces the creation process. Because I still think that the connection recycling operation is necessary, because I cannot guarantee that my program will not generate errors during operation.
Thank you very much for your support
Rediscan work in “single_connection_client” mode, but normally it doesn’t. In Sentinel mode it does not. and__aenter__()does nothing. Don’t worry about single connection mode.(Actually, a single connection client is created when you call
redis.client(), … I’ll get to that later.)In normal mode, this is what happens:
redis=Redis()is created. An internalConnectionPoolis created.await redis.execute_commandindirecly via e.g.await redis.get(). This is what happens a. a connection is got from the pool, maybe created b. the command is executed c. the connection is put back to the pool.await redis.aclose(). This closes all connections in the pool.The purpose of
async withis chiefly to remember to callredis.close(), which again is to callredis.connection_pool.close(). You only callredis.aclose()if you do not want to use this redis instance again.Redis.client()But it is also possible to use the
client()method, to get a private client connection if you want to do many things with it on the sameTask:redis=Redis()is created. An internalConnectionPoolis created.cli = redis.client(). This creates a single connection client, borrowing the same pool asredisasync with client, will get the connection, allowing you to use it, and _return it to the pol after`Your case:
Now, as I understand it:
master_for(), that can re-use the connections for different “requests”The answer is simple: do not call
redis.aclose(). Keep it open. Do not useasync withon the master Redis() object.You can use
redis.client()as in your code. That will create a new single_connection_client, using the same pool as the parent.solution 1. No
client()You just use the same master Redis() object for each connection
solution 2: with
redis.client()Here you to create a private Redis object for each request
Every time you call
slave_for()a new Redis() instance is created, with a new ConnectionPool… I see in your code that you do this globally. Therefore, these objects will not get deleted, they will live forever. The change in 5.0.1 was to make sure that when you close the client, you also close this automatically created connection pool, otherwise, there would be no way to close it.Your problem is a different one. You don’t want the “async with”. You are keeping the
Redisobjects that you got frommaster_for()andslave_for(), in a global dict, and not deleting / closing them. theasync withis only about automatically callingRedis.aclose()for you. This will close the connection pool. Your solution, is to not close them, not use theasync for.Instead, just do the following:
the
async withpattern is for ensuring that you close all connections after you are done with them:Cheers!
Yes, we do spawn threads for some jobs, but not for request handling. Threads we spawn do not interfere with asyncio/redis part. At least to my knowledge. We’re using FastAPI as our web-framework. I’ll look it up, thanks for the tip!
@kristjanvalur Based on my tests, it looks like your PR indeed fixed the issue with the ConnectionError raised ! Thanks again for taking care of this, this quickly.
@kristjanvalur Thanks for being so reactive ! I’m currently trying to reproduce the issue as I did not yet find a consistent trigger for the “No connection available” error. I’ll keep you posted !