-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix async client safety #3512
base: master
Are you sure you want to change the base?
Fix async client safety #3512
Conversation
When the async Redis client is used as an async context manager and called from different corotuines, one coroutine can exit, shutting down the client's connection pool, while another coroutine is attempting to use a connection. This results in a connection error, such as: redis.exceptions.ConnectionError: Connection closed by server. Additional locking in `ConnectionPool` resolves the problem but introduces extreme latency due to the locking. Instead, this PR implements a shielded counter that increments as callers enter the async context manager and decrements when they exit. The client then closes its connection pool only after all active contexts exit. Performance is on par with use of the client without a context manager.
self._usage_counter -= 1 | ||
raise | ||
|
||
async def _decrement_usage(self) -> int: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A helper method is required so we can use it in the shield()
.
@abrookins Appreciate your contribution! We’ll review your change soon. |
Is there an ETA on when this will land on a new release? This is a critical blocker for LiteLLM users @petyaslavova Anything I can do to help accelerate this ? |
return await self.initialize() | ||
except Exception: | ||
# If initialization fails, decrement the counter to keep it in sync | ||
async with self._usage_lock: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can directly use the new function _decrement_usage here. The same applies to cluster.py changes as well.
connection pool is only closed (via aclose()) when no context is using | ||
the client. | ||
""" | ||
async with self._usage_lock: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest adding another function _increment_usage for this operation. It will be easier to read and follow the code if here you call _increment_usage and below in the except clause you call _decrement_usage. The same applies to cluster.py changes as well.
Fix async safety when Redis client is used as an async context manager.
When the async Redis client is used as an async context manager and
called from different corotuines, one coroutine can exit, shutting
down the client's connection pool, while another coroutine is
attempting to use a connection. This results in a connection error,
such as:
Additional locking in
ConnectionPool
resolves the problem butintroduces extreme latency due to the locking. Instead, this PR
implements a shielded counter that increments as callers enter the async
context manager and decrements when they exit. The client then closes
its connection pool only after all active contexts exit.
Performance is on par with use of the client without a context manager.
Reproducing the issue
You should be able to reproduce the connection error with the following
script, using the "manager" argument. Running the script with the
"no-manager" argument should not produce any errors.
With this patch applied, the error no longer occurs.
Pull Request check-list
Please make sure to review and check all of these items:
NOTE: these things are not required to open a PR and can be done
afterwards / while the PR is open.
Description of change
Please provide a description of the change here.