Repository navigation
"loop argument must agree with lock" instantiating asyncio.Condition #89579
Description
Activity
In Python 3.10 it is not possible to instantiate an asyncio.Condition that wraps an asyncio.Lock without raising a "loop argument must agree with lock" exception.
This code raises that exception:
asyncio.Condition(asyncio.Lock())
This worked in previous Python versions.
Note that the error only occurs if an event loop is running. Here's a simple script that replicates the problem:
import asyncio # This runs without an exception: print(asyncio.Condition(asyncio.Lock())) # This does not work: async def example(): print(asyncio.Condition(asyncio.Lock())) # This raises "ValueError: loop argument must agree with lock": asyncio.run(example())
I ran across this issue while trying to use the https://pypi.org/project/janus/ locking library with Python 3.10 - see my issue on their tracker here: aio-libs/janus#358
It looks like the relevant test is here:
cpython/Lib/test/test_asyncio/test_locks.py
Lines 722 to 727 in a1092f6
def test_explicit_lock(self): lock = asyncio.Lock() cond = asyncio.Condition(lock) self.assertIs(cond._lock, lock) self.assertIs(cond._loop, lock._loop) def test_explicit_lock(self): lock = asyncio.Lock() cond = asyncio.Condition(lock) self.assertIs(cond._lock, lock) self.assertIs(cond._loop, lock._loop)
But... that test doesn't appear to run inside an event loop, so it's not covering the behaviour described in this issue.
Issue confirmed. The problem is that
Condition.__init__compares its own loop with Lock's internal_loopattribute. That attribute is set to None unless the lock waited to be acquired.uriyyo, Andrew, Yury, since it's pretty likely that Lock objects will now have
_loopset to None, I think the check for "loop agreement" in Condition is kind of useless. So I propose to simply remove it. It's the only such check in all of asyncio.If you insist on keeping the loop check, it should special-case
_loop is None.Simon, thanks for your detailed report! As a backwards-compatible workaround till we get a fix in, your user code can do the following:
>>> l = asyncio.Lock() >>> getattr(l, '_get_loop', lambda: None)() <_UnixSelectorEventLoop running=True closed=False debug=False>
You can use such lock without issues now:
>>> asyncio.Condition(l) <asyncio.locks.Condition object at 0x10c05bee0 [unlocked]>
Alternatively, if the above disgusts you and you only want to trigger public APIs, you can do this dance:
>>> l = asyncio.Lock() >>> await l.acquire() # first acquire will just work True >>> try: ... # second acquire will block so we time it out ... await asyncio.wait_for(l.acquire(), 0.1) ... except asyncio.TimeoutError: ... pass ... >>> l.release()
Now the lock is fully initialized and we can use it:
>> c = asyncio.Condition(l)
Both workarounds should be compatible with Python 3.7+ asyncio.
Thanks, guys!
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields: