fix(stability): test the HTTP semaphore balance + correct the dedup docstring (review round 1)

F1 [WARNING] The HTTP_DOWNLOAD_SEMAPHORE admission-control balance was untested,
and it is a plain asyncio.Semaphore — so an over-release would SILENTLY inflate
the permit count and disable the limiter (no ValueError), a green suite hiding the
exact bug stage 2 fixes. Added two tests (mirroring the stage-1 tg_throttle
balance test): the permit is released exactly once when the download errors (count
back to baseline), and a timed-out acquire (503) releases NOTHING (count
unchanged). Adversarially validated: neutralizing the finally release makes the
first test fail, so it genuinely catches a leak.

F2 [low] Corrected the _download_deduped docstring: the detached task sets the
Future in try/except, and its finally ALWAYS pops the key — the previous wording
attributed the Future-set to the finally, which an auditor reading the finally
block would find contradicted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
claude code agent
2026-07-05 07:56:23 +03:00
parent 444bd3e42f
commit 41d143458f
2 changed files with 65 additions and 3 deletions
+4 -3
View File
@@ -438,9 +438,10 @@ async def _download_deduped(channel: Union[str, int], post_id: int, file_unique_
The first request for a key runs download_media_file in a DETACHED task and shares its
Future; concurrent requests await the same Future (bounded by wait_for). The detached
task's finally BOTH sets the Future's result/exception AND pops the key — so a
completed Future never leaves the key stuck, and a client disconnect (cancelling only
the awaiting request coroutine) can neither cancel the download nor hang other waiters.
task sets the Future's result/exception (on success/failure) and its finally ALWAYS
pops the key — so both happen before the task ends: a completed Future never leaves the
key stuck, and a client disconnect (cancelling only the awaiting request coroutine) can
neither cancel the download nor hang other waiters.
"""
key = (str(channel), post_id, file_unique_id)
fut = _inflight.get(key)