fix(credential_pool): unpack the tuple in next_available_at's gate

Cross-PR interaction fix: #77714 (salvage of #71775) changed
_available_entries to return (available, pending_refresh) while #77631
(salvage of #67642) added next_available_at() which still truthiness-
tests the bare return. A non-empty tuple is always truthy — even
([], []) — so the reset-aware gate silently returned None ('no wait
info') for every exhausted pool, disabling the feature #77631 shipped.
Unpack the tuple and test the available list.

Also adapts the lock-probe test for the RLock introduced by #77714
(same-thread non-blocking acquire always succeeds on an RLock; probe
from a helper thread instead).
This commit is contained in:
kshitijk4poor
2026-08-03 19:16:16 +05:30
committed by kshitij
parent 13172fca02
commit 82019e7c1b
2 changed files with 19 additions and 4 deletions
+2 -1
View File
@@ -642,7 +642,8 @@ class CredentialPool:
``_available_entries`` caller (see the comment on ``has_available``).
"""
with self._lock:
if self._available_entries():
available, _pending = self._available_entries()
if available:
return None
candidates: List[float] = []
for entry in self._entries:
@@ -203,9 +203,23 @@ class TestNextAvailableAt:
original = pool._available_entries
def _probe(**kwargs):
held["locked"] = not pool._lock.acquire(blocking=False)
if not held["locked"]:
pool._lock.release()
# self._lock is an RLock (deferred-refresh mutations self-lock),
# so a same-thread non-blocking acquire always succeeds; probe
# ownership from a helper thread instead.
import threading as _t
blocked = _t.Event()
def _try():
if not pool._lock.acquire(blocking=False):
blocked.set()
else:
pool._lock.release()
worker = _t.Thread(target=_try)
worker.start()
worker.join(timeout=5)
held["locked"] = blocked.is_set()
return original(**kwargs)
pool._available_entries = _probe