]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-45416: Fix use of asyncio.Condition() with explicit Lock objects (GH-28850)
authorJoongi Kim <joongi@lablup.com>
Sun, 10 Oct 2021 16:01:41 +0000 (01:01 +0900)
committerGitHub <noreply@github.com>
Sun, 10 Oct 2021 16:01:41 +0000 (19:01 +0300)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Lib/asyncio/locks.py
Lib/test/test_asyncio/test_locks.py
Misc/NEWS.d/next/Library/2021-10-10-09-42-34.bpo-45416.n35O0_.rst [new file with mode: 0644]

index a7453fb1c77287898bedeaf3828daafe49b0f013..4fef64e3921e17004f2dbd16426002daea85fae2 100644 (file)
@@ -230,8 +230,6 @@ class Condition(_ContextManagerMixin, mixins._LoopBoundMixin):
         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.
index 441adeea8f8e076ecb152e631eb2491f9959257f..b2492c1acfecef7edccee28fcc383120abe6a4c2 100644 (file)
@@ -720,24 +720,68 @@ class ConditionTests(test_utils.TestCase):
         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 (file)
index 0000000..cf335d1
--- /dev/null
@@ -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.\r
+Patch by Joongi Kim.
\ No newline at end of file