Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions asyncpg/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link
Member

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.

"""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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These attributes are basically always available, so getattr is superfluous, just access the attributes directly.

Copy link
Author

Choose a reason for hiding this comment

The 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except this one does not seem to actually exist?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add that and it'll let us to drop the queue._getters fiddling above.


# 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()
Expand Down
Loading