5b794c984e
`has_available()`, `peek()`, `current()` and `entries()` read (and, via `_available_entries()`, mutate and persist) `self._entries` without holding `self._lock`, while every other entry point — `select()`, `mark_exhausted_and_rotate()`, `acquire_lease()`, `try_refresh_current()` — guards the exact same access with the lock. `_available_entries()` is not read-only: it prunes aged-out DEAD manual entries (rebinding `self._entries` at the prune step) and calls `_persist()` (writes auth.json). The gateway runs platform adapters in threads and cron runs jobs in a ThreadPoolExecutor, so a status probe via `has_available()` or `peek()` can race a concurrent `select()`/rotation: torn iteration of `self._entries`, interleaved auth.json writes, or a lost token rotation. Fix: take `self._lock` in all four query methods. Because the lock is non-reentrant and `peek()` composes `current()` + `_available_entries()`, add a lock-free `_current_unlocked()` helper and route the already-locked internal callers (`_select_unlocked`, `mark_exhausted_and_rotate`, `_try_refresh_current_unlocked`) through it to avoid self-deadlock. Added regression tests: a no-deadlock check (peek re-entrancy) and a lock-held-blocks-the-call check for each of the four methods.