GeezerVison is showing github.com — Make `asyncio.Lock.locked()` return `True` when there are waiters · Issue #138121 · python/cpython
Make asyncio.Lock.locked() return True when there are waiters #138121DescriptionBug report
Bug description:
In issue #97028 everyone seemed to agree that asyncio.{Lock,Semaphore}.locked() should return True if there are waiters, but this change was implemented only for Semaphore.
CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Linked PRs
Activityashm-dev commented on Sep 8, 2025 Can I try to fix this issue?
Just to clarify: am I correct that the main point of this issue is to backport the existing fix (already applied in 3.12+) into the <3.12 branches? picnixz commented on Sep 8, 2025 Since we don't know the answer to the question I wouldn't say there is anything to fix. Let's ask @kumaraditya303 olutra commented on Mar 7, 2026 kumaraditya303 commented on Mar 9, 2026 I don't quite remember the details, maybe @gvanrossum remembers? if this should be case for Lock as well. gvanrossum commented on Mar 9, 2026 Yeah, Lock.locked() should definitely return true if there are waiters. changed the title [-]Should `asyncio.Lock.locked()` return `True` when there are waiters?[/-] [+]Make `asyncio.Lock.locked()` return `True` when there are waiters[/+] on Mar 9, 2026gvanrossum commented on Mar 10, 2026 Small caveat: Maybe you can prove (or disprove!) that when there are waiters, the locked flag is always set? johng commented on Jul 17, 2026 We can see in Semaphore we have
def locked(self):
"""Returns True if semaphore cannot be acquired immediately."""
# Due to state, or FIFO rules (must allow others to run first).
return self._value == 0 or (
any(not w.cancelled() for w in (self._waiters or ())))
For Lock we have this driven by the state of the Lock
def locked(self):
"""Return True if lock is acquired."""
return self._locked
This gets unset once releasing the lock and I don't see any logic for checking for waiters first.
def release(self):
if self._locked:
self._locked = False
self._wake_up_first()
else:
raise RuntimeError('Lock is not acquired.')
johng commented on Jul 17, 2026 Was able to reproduce this quite easily with some AI help
import asyncio
async def main():
lock = asyncio.Lock()
sem = asyncio.Semaphore(1)
await lock.acquire()
await sem.acquire()
async def wait_lock():
async with lock:
pass
async def wait_sem():
async with sem:
pass
tl = asyncio.create_task(wait_lock())
ts = asyncio.create_task(wait_sem())
await asyncio.sleep(0) # both tasks are now parked in acquire()
# Release, then inspect *synchronously* (no await) so the woken waiter
# has NOT run yet.
lock.release()
sem.release()
print("Immediately after release(), with a waiter still queued:")
print(f" lock._locked={lock._locked}, "
f"lock._waiters={len(lock._waiters or ())} -> lock.locked() = {lock.locked()}")
print(f" sem._value={sem._value}, "
f"sem._waiters={len(sem._waiters or ())} -> sem.locked() = {sem.locked()}")
if lock.locked() != sem.locked():
print("\n>>> INCONSISTENCY REPRODUCED: identical situation, "
"Lock says free, Semaphore says locked.")
else:
print("\n>>> no inconsistency (already fixed?)")
await tl
await ts
asyncio.run(main())
uv run repro_138121.py
Immediately after release(), with a waiter still queued:
lock._locked=False, lock._waiters=1 -> lock.locked() = False
sem._value=0, sem._waiters=1 -> sem.locked() = True
>>> INCONSISTENCY REPRODUCED: identical situation, Lock says free, Semaphore says locked.
EmBeckwith commented on Jul 20, 2026 Heya new to this but I've made a PR with a fairly naive solution here if someone wants to take a look
MetadataMetadataStatusShow more project fields
DevelopmentNo branches or pull requestsIssue actions
|