-
Notifications
You must be signed in to change notification settings - Fork 435
Pool: prevent trimming the last idle connection under load #1271
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
base: master
Are you sure you want to change the base?
Changes from all commits
b4b950c
48dea1e
e327332
2c95068
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -287,12 +287,70 @@ def _maybe_cancel_inactive_callback(self) -> None: | |
self._inactive_callback.cancel() | ||
self._inactive_callback = None | ||
|
||
def _can_deactivate_inactive_connection(self) -> bool: | ||
"""Return True if an idle connection may be deactivated (trimmed). | ||
|
||
Constraints: | ||
- Do not trim if there are waiters in the pool queue (including acquiring). | ||
- Do not trim below pool min size (leave at least `minsize` open connections). | ||
- Keep at least one idle connection available (i.e., at least 2 idle holders so | ||
trimming one still leaves one idle). | ||
""" | ||
pool = getattr(self, "_pool", None) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These attributes are basically always available, so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point thanks |
||
if pool is None: | ||
# No pool state; allow default trimming behavior. | ||
return True | ||
|
||
# Follow original logic: if pool is closing, handle as default (allow trim). | ||
if getattr(pool, "_closing", False): | ||
return True | ||
|
||
minsize = int(getattr(pool, "_minsize", 0) or 0) | ||
|
||
# Compute the number of tasks waiting to acquire a connection. | ||
q = getattr(pool, "_queue", None) | ||
if q is not None: | ||
getters = getattr(q, "_getters", None) | ||
waiters = len(getters) if getters is not None else 0 | ||
else: | ||
waiters = 0 | ||
|
||
# Include tasks currently in the process of acquiring. | ||
waiters += int(getattr(pool, "_acquiring", 0) or 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Except this one does not seem to actually exist? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add that and it'll let us to drop the |
||
|
||
# Count open (live) connections and how many of them are idle. | ||
open_conns = 0 | ||
idle = 0 | ||
holders = list(getattr(pool, "_holders", []) or []) | ||
for h in holders: | ||
if getattr(h, "_con", None) is not None: | ||
open_conns += 1 | ||
if not getattr(h, "_in_use", None): | ||
idle += 1 | ||
|
||
# Conditions to allow trimming one idle connection: | ||
# - No waiters. | ||
# - Trimming one won't drop below minsize (so open_conns - 1 >= minsize). | ||
# - After trimming one idle, at least one idle remains (so idle >= 2). | ||
return ( | ||
waiters == 0 and | ||
(open_conns - 1) >= minsize and | ||
idle >= 2 | ||
) | ||
|
||
def _deactivate_inactive_connection(self) -> None: | ||
if self._in_use is not None: | ||
raise exceptions.InternalClientError( | ||
'attempting to deactivate an acquired connection') | ||
|
||
if self._con is not None: | ||
# Only deactivate if doing so respects pool size and demand constraints. | ||
if not self._can_deactivate_inactive_connection(): | ||
# Still mark this holder as available and keep the connection. | ||
# Re-arm the inactivity timer so we can reevaluate later. | ||
self._setup_inactive_callback() | ||
return | ||
|
||
# The connection is idle and not in use, so it's fine to | ||
# use terminate() instead of close(). | ||
self._con.terminate() | ||
|
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.
It would probably make sense to move the method to the
Pool
class given how much we fiddle with internals here.