[Python-checkins] bpo-45416: Fix use of asyncio.Condition() with explicit Lock objects (GH-28850)

serhiy-storchaka webhook-mailer at python.org
Sun Oct 10 12:01:49 EDT 2021


https://github.com/python/cpython/commit/1a7892414e654aa5c99efa31db767baba7f4a424
commit: 1a7892414e654aa5c99efa31db767baba7f4a424
branch: main
author: Joongi Kim <joongi at lablup.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2021-10-10T19:01:41+03:00
summary:

bpo-45416: Fix use of asyncio.Condition() with explicit Lock objects (GH-28850)

Co-authored-by: Serhiy Storchaka <storchaka at gmail.com>

files:
A Misc/NEWS.d/next/Library/2021-10-10-09-42-34.bpo-45416.n35O0_.rst
M Lib/asyncio/locks.py
M Lib/test/test_asyncio/test_locks.py

diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py
index a7453fb1c7728..4fef64e3921e1 100644
--- a/Lib/asyncio/locks.py
+++ b/Lib/asyncio/locks.py
@@ -230,8 +230,6 @@ def __init__(self, lock=None, *, loop=mixins._marker):
         super().__init__(loop=loop)
         if lock is None:
             lock = Lock()
-        elif lock._loop is not self._get_loop():
-            raise ValueError("loop argument must agree with lock")
 
         self._lock = lock
         # Export the lock's locked(), acquire() and release() methods.
diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py
index 441adeea8f8e0..b2492c1acfece 100644
--- a/Lib/test/test_asyncio/test_locks.py
+++ b/Lib/test/test_asyncio/test_locks.py
@@ -720,24 +720,68 @@ async def f():
         self.loop.run_until_complete(f())
 
     def test_explicit_lock(self):
-        lock = asyncio.Lock()
-        cond = asyncio.Condition(lock)
+        async def f(lock=None, cond=None):
+            if lock is None:
+                lock = asyncio.Lock()
+            if cond is None:
+                cond = asyncio.Condition(lock)
+            self.assertIs(cond._lock, lock)
+            self.assertFalse(lock.locked())
+            self.assertFalse(cond.locked())
+            async with cond:
+                self.assertTrue(lock.locked())
+                self.assertTrue(cond.locked())
+            self.assertFalse(lock.locked())
+            self.assertFalse(cond.locked())
+            async with lock:
+                self.assertTrue(lock.locked())
+                self.assertTrue(cond.locked())
+            self.assertFalse(lock.locked())
+            self.assertFalse(cond.locked())
 
-        self.assertIs(cond._lock, lock)
-        self.assertIs(cond._loop, lock._loop)
+        # All should work in the same way.
+        self.loop.run_until_complete(f())
+        self.loop.run_until_complete(f(asyncio.Lock()))
+        lock = asyncio.Lock()
+        self.loop.run_until_complete(f(lock, asyncio.Condition(lock)))
 
     def test_ambiguous_loops(self):
-        loop = self.new_test_loop()
+        loop = asyncio.new_event_loop()
         self.addCleanup(loop.close)
 
-        lock = asyncio.Lock()
-        lock._loop = loop
-
-        async def _create_condition():
-            with self.assertRaises(ValueError):
-                asyncio.Condition(lock)
-
-        self.loop.run_until_complete(_create_condition())
+        async def wrong_loop_in_lock():
+            with self.assertRaises(TypeError):
+                asyncio.Lock(loop=loop)  # actively disallowed since 3.10
+            lock = asyncio.Lock()
+            lock._loop = loop  # use private API for testing
+            async with lock:
+                # acquired immediately via the fast-path
+                # without interaction with any event loop.
+                cond = asyncio.Condition(lock)
+                # cond.acquire() will trigger waiting on the lock
+                # and it will discover the event loop mismatch.
+                with self.assertRaisesRegex(
+                    RuntimeError,
+                    "is bound to a different event loop",
+                ):
+                    await cond.acquire()
+
+        async def wrong_loop_in_cond():
+            # Same analogy here with the condition's loop.
+            lock = asyncio.Lock()
+            async with lock:
+                with self.assertRaises(TypeError):
+                    asyncio.Condition(lock, loop=loop)
+                cond = asyncio.Condition(lock)
+                cond._loop = loop
+                with self.assertRaisesRegex(
+                    RuntimeError,
+                    "is bound to a different event loop",
+                ):
+                    await cond.wait()
+
+        self.loop.run_until_complete(wrong_loop_in_lock())
+        self.loop.run_until_complete(wrong_loop_in_cond())
 
     def test_timeout_in_block(self):
         loop = asyncio.new_event_loop()
diff --git a/Misc/NEWS.d/next/Library/2021-10-10-09-42-34.bpo-45416.n35O0_.rst b/Misc/NEWS.d/next/Library/2021-10-10-09-42-34.bpo-45416.n35O0_.rst
new file mode 100644
index 0000000000000..cf335d1bcd2c9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-10-10-09-42-34.bpo-45416.n35O0_.rst
@@ -0,0 +1,2 @@
+Fix use of :class:`asyncio.Condition` with explicit :class:`asyncio.Lock` objects, which was a regression due to removal of explicit loop arguments.
+Patch by Joongi Kim.
\ No newline at end of file



More information about the Python-checkins mailing list