From f98d671ac11252d7920dbbfb406a8ad30a4c4f42 Mon Sep 17 00:00:00 2001 From: Mathieu Sornay Date: Wed, 16 Nov 2016 12:49:24 +0100 Subject: [PATCH 1/2] Cancelled lock waiter wakes up the next one if any --- asyncio/locks.py | 17 ++++++++++++----- tests/test_pep492.py | 25 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/asyncio/locks.py b/asyncio/locks.py index deefc938..92661830 100644 --- a/asyncio/locks.py +++ b/asyncio/locks.py @@ -176,6 +176,10 @@ def acquire(self): yield from fut self._locked = True return True + except futures.CancelledError: + if not self._locked: + self._wake_up_first() + raise finally: self._waiters.remove(fut) @@ -192,14 +196,17 @@ def release(self): """ if self._locked: self._locked = False - # Wake up the first waiter who isn't cancelled. - for fut in self._waiters: - if not fut.done(): - fut.set_result(True) - break + self._wake_up_first() else: raise RuntimeError('Lock is not acquired.') + def _wake_up_first(self): + """Wake up the first waiter who isn't cancelled.""" + for fut in self._waiters: + if not fut.done(): + fut.set_result(True) + break + class Event: """Asynchronous equivalent to threading.Event. diff --git a/tests/test_pep492.py b/tests/test_pep492.py index d5b85224..3882d835 100644 --- a/tests/test_pep492.py +++ b/tests/test_pep492.py @@ -71,6 +71,31 @@ async def test(lock): self.loop.run_until_complete(test(primitive)) self.assertFalse(primitive.locked()) + def test_finished_waiter_cancelled(self): + + async def create_waiter(lock, fut): + fut.set_result(True) + async with lock: + pass + + async def runner(): + lock = asyncio.Lock(loop=self.loop) + + await lock.acquire() + + fut = self.loop.create_future() + task = asyncio.ensure_future(create_waiter(lock, fut), + loop=self.loop) + await fut + + lock.release() + task.cancel() + + async with lock: + pass + + self.loop.run_until_complete(runner()) + class StreamReaderTests(BaseTest): From 3bc7f442512a4191780b530c8de3e8938ba91972 Mon Sep 17 00:00:00 2001 From: msornay Date: Fri, 18 Nov 2016 00:41:59 +0100 Subject: [PATCH 2/2] Lock waiter cancel race test Python issue #27585 --- tests/test_locks.py | 23 +++++++++++++++++++++++ tests/test_pep492.py | 25 ------------------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/tests/test_locks.py b/tests/test_locks.py index 152948c8..fab97521 100644 --- a/tests/test_locks.py +++ b/tests/test_locks.py @@ -176,6 +176,29 @@ def lockit(name, blocker): self.assertTrue(tb.cancelled()) self.assertTrue(tc.done()) + def test_finished_waiter_cancelled(self): + lock = asyncio.Lock(loop=self.loop) + + ta = asyncio.Task(lock.acquire(), loop=self.loop) + test_utils.run_briefly(self.loop) + self.assertTrue(lock.locked()) + + tb = asyncio.Task(lock.acquire(), loop=self.loop) + test_utils.run_briefly(self.loop) + self.assertEqual(len(lock._waiters), 1) + + # Create a second waiter, wake up the first, and cancel it. + # Without the fix, the second was not woken up. + tc = asyncio.Task(lock.acquire(), loop=self.loop) + lock.release() + tb.cancel() + test_utils.run_briefly(self.loop) + + self.assertTrue(lock.locked()) + self.assertTrue(ta.done()) + self.assertTrue(tb.cancelled()) + self.assertTrue(tc.done()) + def test_release_not_acquired(self): lock = asyncio.Lock(loop=self.loop) diff --git a/tests/test_pep492.py b/tests/test_pep492.py index 3882d835..d5b85224 100644 --- a/tests/test_pep492.py +++ b/tests/test_pep492.py @@ -71,31 +71,6 @@ async def test(lock): self.loop.run_until_complete(test(primitive)) self.assertFalse(primitive.locked()) - def test_finished_waiter_cancelled(self): - - async def create_waiter(lock, fut): - fut.set_result(True) - async with lock: - pass - - async def runner(): - lock = asyncio.Lock(loop=self.loop) - - await lock.acquire() - - fut = self.loop.create_future() - task = asyncio.ensure_future(create_waiter(lock, fut), - loop=self.loop) - await fut - - lock.release() - task.cancel() - - async with lock: - pass - - self.loop.run_until_complete(runner()) - class StreamReaderTests(BaseTest):