fix(tasks): clean up singleflight cache on cancellation (#6174)

Signed-off-by: daixiheguu <daixihegu@outlook.com>
This commit is contained in:
daixiheguu
2026-09-01 18:34:50 +02:00
committed by GitHub
parent 5154bae544
commit ce04dc1db4
2 changed files with 101 additions and 4 deletions
+15 -4
View File
@@ -84,19 +84,30 @@ async def _cached(key: Tuple, ttl: float, fetch: Callable[[], Awaitable[Any]]) -
pending = fut
owner = True
if not owner:
return await pending
# A cancelled waiter must not cancel the shared Future for the owner
# and every other waiter.
return await asyncio.shield(pending)
try:
val = await fetch()
async with _shared_cache_lock:
_shared_cache[key] = (time.monotonic() + ttl, val)
_shared_cache_pending.pop(key, None)
pending.set_result(val)
return val
except asyncio.CancelledError:
# Cancellation is a BaseException on supported Python versions, so it
# bypasses the Exception handler below. Wake all current waiters while
# allowing a later caller to retry the fetch.
pending.cancel()
raise
except Exception as e:
async with _shared_cache_lock:
_shared_cache_pending.pop(key, None)
pending.set_exception(e)
raise
finally:
# Keep this cleanup synchronous so a second cancellation cannot
# interrupt it and leave a permanently pending Future behind. All
# access runs on the scheduler's event-loop thread.
if _shared_cache_pending.get(key) is pending:
_shared_cache_pending.pop(key, None)
def compute_next_run(schedule: str, scheduled_time: str,