bpo-32734: Fix asyncio.Lock multiple acquire safety issue (GH-5466)

This commit is contained in:
Bar Harel 2018-02-03 00:04:00 +02:00 committed by Yury Selivanov
parent 66771422d0
commit 2f79c01493
4 changed files with 75 additions and 10 deletions

View file

@ -183,16 +183,22 @@ class Lock(_ContextManagerMixin):
fut = self._loop.create_future()
self._waiters.append(fut)
# Finally block should be called before the CancelledError
# handling as we don't want CancelledError to call
# _wake_up_first() and attempt to wake up itself.
try:
await fut
self._locked = True
return True
try:
await fut
finally:
self._waiters.remove(fut)
except futures.CancelledError:
if not self._locked:
self._wake_up_first()
raise
finally:
self._waiters.remove(fut)
self._locked = True
return True
def release(self):
"""Release a lock.
@ -212,11 +218,17 @@ class Lock(_ContextManagerMixin):
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
"""Wake up the first waiter if it isn't done."""
try:
fut = next(iter(self._waiters))
except StopIteration:
return
# .done() necessarily means that a waiter will wake up later on and
# either take the lock, or, if it was cancelled and lock wasn't
# taken already, will hit this again and wake up a new waiter.
if not fut.done():
fut.set_result(True)
class Event: