From a23eecab9a0b724bdfde83d159ac2415927f042a Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 14 Feb 2018 02:10:18 -0800 Subject: [PATCH] bpo-32841: Fix cancellation in awaiting asyncio.Condition (GH-5665) (GH-5683) (cherry picked from commit 5746510b7aef423fa4afc92b2abb919307b1dbb9) Co-authored-by: Bar Harel --- Lib/asyncio/locks.py | 6 +++- Lib/test/test_asyncio/test_locks.py | 31 ++++++++++++++++--- .../2018-02-14-00-21-24.bpo-32841.bvHDOc.rst | 2 ++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-02-14-00-21-24.bpo-32841.bvHDOc.rst diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index 14d21ff4e6..8ee7e2ea04 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -349,12 +349,16 @@ class Condition(_ContextManagerMixin): finally: # Must reacquire lock even if wait is cancelled + cancelled = False while True: try: yield from self.acquire() break except futures.CancelledError: - pass + cancelled = True + + if cancelled: + raise futures.CancelledError @coroutine def wait_for(self, predicate): diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py index 835d09ffc5..8686395da1 100644 --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -190,11 +190,11 @@ class LockTests(test_utils.TestCase): call_count += 1 await lock.acquire() lock_count += 1 - + async def lockandtrigger(): await lock.acquire() self.loop.call_soon(trigger) - + def trigger(): t1.cancel() lock.release() @@ -224,8 +224,6 @@ class LockTests(test_utils.TestCase): test_utils.run_briefly(self.loop) self.assertTrue(t3.cancelled()) - - def test_finished_waiter_cancelled(self): lock = asyncio.Lock(loop=self.loop) @@ -557,6 +555,31 @@ class ConditionTests(test_utils.TestCase): self.assertTrue(cond.locked()) + def test_wait_cancel_after_notify(self): + # See bpo-32841 + cond = asyncio.Condition(loop=self.loop) + waited = False + + async def wait_on_cond(): + nonlocal waited + async with cond: + waited = True # Make sure this area was reached + await cond.wait() + + waiter = asyncio.ensure_future(wait_on_cond(), loop=self.loop) + test_utils.run_briefly(self.loop) # Start waiting + + self.loop.run_until_complete(cond.acquire()) + cond.notify() + test_utils.run_briefly(self.loop) # Get to acquire() + waiter.cancel() + test_utils.run_briefly(self.loop) # Activate cancellation + cond.release() + test_utils.run_briefly(self.loop) # Cancellation should occur + + self.assertTrue(waiter.cancelled()) + self.assertTrue(waited) + def test_wait_unacquired(self): cond = asyncio.Condition(loop=self.loop) self.assertRaises( diff --git a/Misc/NEWS.d/next/Library/2018-02-14-00-21-24.bpo-32841.bvHDOc.rst b/Misc/NEWS.d/next/Library/2018-02-14-00-21-24.bpo-32841.bvHDOc.rst new file mode 100644 index 0000000000..a6d45669d0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-02-14-00-21-24.bpo-32841.bvHDOc.rst @@ -0,0 +1,2 @@ +Fixed `asyncio.Condition` issue which silently ignored cancellation after +notifying and cancelling a conditional lock. Patch by Bar Harel. -- 2.40.0