8000 GH-111693: Propagate correct asyncio.CancelledError instance out of asyncio.Condition.wait() by kristjanvalur · Pull Request #111694 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

GH-111693: Propagate correct asyncio.CancelledError instance out of asyncio.Condition.wait() #111694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Suggestsions from code review.
  • Loading branch information
kristjanvalur committed Nov 26, 2023
commit 62bd6cdc420f536798741296c7be057ce2e72f09
30 changes: 17 additions & 13 deletions Lib/asyncio/locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,12 @@ async def acquire(self):
await fut
finally:
self._waiters.remove(fut)
except BaseException:
except exceptions.CancelledError:
# Currently the only exception designed be able to occur here.

# Ensure the lock invariant: If lock is not claimed (or about to be by us)
# and there is a Task in waiters,
# ensure that that Task (now at the head) will run.
# ensure that the Task at the head will run.
if not self._locked:
self._wake_up_first()
raise
Expand All @@ -143,17 +145,15 @@ def release(self):
raise RuntimeError('Lock is not acquired.')

def _wake_up_first(self):
"""Wake up the first waiter if it isn't done."""
"""Ensure that the first waiter will wake up."""
if not self._waiters:
return
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.
# .done() means that the waiter is already set to wake up
if not fut.done():
fut.set_result(True)

Expand Down Expand Up @@ -392,18 +392,21 @@ async def acquire(self):
await fut
finally:
self._waiters.remove(fut)
except BaseException:
except exceptions.CancelledError:
# Currently the only exception designed be able to occur here.
if fut.done() and not fut.cancelled():
# Our Future was successfully set to True via _wake_up_next(),
# but we are not about to successfully acquire(). Therefore we
# must undo the bookkeeping already done and attempt to wake
# up someone else.
self._value += 1
self._wake_up_next()
raise

if self._value > 0:
self._wake_up_next()
finally:
# new waiters may have arrived. Wake up as many as are allowed
while self._value > 0:
if not self._wake_up_next():
break # there was no-one to wake up
return True

def release(self):
Expand All @@ -418,14 +421,15 @@ def release(self):
def _wake_up_next(self):
"""Wake up the first waiter that isn't done."""
if not self._waiters:
return
return False

for fut in self._waiters:
if not fut.done():
self._value -= 1
fut.set_result(True)
# assert fut.true() and not fut.cancelled()
return
# fut is now `done()` and not `cancelled()`
return True
return False


class BoundedSemaphore(Semaphore):
Expand Down
10 changes: 4 additions & 6 deletions Lib/test/test_asyncio/test_locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,9 +759,8 @@ async def test_timeout_in_block(self):
await asyncio.wait_for(condition.wait(), timeout=0.5)

async def test_cancelled_error_wakeup(self):
"""Test that a cancelled error, received when awaiting wakeup
will be re-raised un-modified.
"""
# Test that a cancelled error, received when awaiting wakeup
# will be re-raised un-modified.
wake = False
raised = None
cond = asyncio.Condition()
Expand All @@ -785,9 +784,8 @@ async def func():
self.assertIs(err.exception, raised)

async def test_cancelled_error_re_aquire(self):
"""Test that a cancelled error, received when re-aquiring lock,
will be re-raised un-modified.
"""
# Test that a cancelled error, received when re-aquiring lock,
# will be re-raised un-modified.
wake = False
raised = None
cond = asyncio.Condition()
Expand Down
0