Skip to content

Commit 9369d80

Browse files
committed
Fixed Condition.wait() not handing over notification when cancelled
Closes #1075.
1 parent 6f122ab commit 9369d80

3 files changed

Lines changed: 25 additions & 0 deletions

File tree

docs/versionhistory.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
2626
- Fixed cancellation exceptions leaking from a ``CancelScope`` on asyncio when they are
2727
contained in an exception group alongside non-cancellation exceptions (`#1091
2828
<https://github.com/agronholm/anyio/issues/1091>`_; PR by @gschaffner)
29+
- Fixed ``Condition.wait()`` not passing on a notification when the task is cancelled
30+
but already received a notification
2931

3032
**4.12.1**
3133

src/anyio/_core/_synchronization.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,10 @@ async def wait(self) -> None:
335335
except BaseException:
336336
if not event.is_set():
337337
self._waiters.remove(event)
338+
elif self._waiters:
339+
# This task was notified by could not act on it, so pass
340+
# it on to the next task
341+
self._waiters.popleft().set()
338342

339343
raise
340344
finally:

tests/test_synchronization.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import sys
5+
from contextlib import AbstractContextManager
56
from typing import Any
67

78
import pytest
@@ -400,6 +401,24 @@ async def task() -> None:
400401
assert task_started
401402
assert not notified
402403

404+
async def test_notification_handover_on_cancel(self) -> None:
405+
condition = Condition()
406+
407+
async def acquirer(scope: AbstractContextManager[CancelScope]) -> None:
408+
with scope:
409+
async with condition:
410+
await condition.wait()
411+
412+
async with create_task_group() as tg:
413+
scope1 = CancelScope()
414+
scope2 = fail_after(3)
415+
tg.start_soon(acquirer, scope1)
416+
tg.start_soon(acquirer, scope2)
417+
await wait_all_tasks_blocked()
418+
async with condition:
419+
scope1.cancel()
420+
condition.notify(1)
421+
403422
async def test_wait_no_lock(self) -> None:
404423
condition = Condition()
405424
with pytest.raises(

0 commit comments

Comments
 (0)