Skip to content

Commit c7d8e69

Browse files
Make NopReceiver.ready() respond to close() method
The ready() method was not terminating when close() was called, causing it to hang indefinitely. This fix ensures ready() returns False when the receiver is closed. Signed-off-by: Elzbieta Kotulska <[email protected]>
1 parent 6f24b25 commit c7d8e69

File tree

3 files changed

+32
-7
lines changed

3 files changed

+32
-7
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414

1515
## Bug Fixes
1616

17-
<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
17+
- Fix `NopReceiver.ready()` to properly terminate when receiver is closed.

src/frequenz/channels/experimental/_nop_receiver.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class NopReceiver(Receiver[ReceiverMessageT_co]):
2020

2121
def __init__(self) -> None:
2222
"""Initialize this instance."""
23-
self._closed: bool = False
23+
self._ready_future: asyncio.Future[bool] = asyncio.Future()
2424

2525
@override
2626
async def ready(self) -> bool:
@@ -29,10 +29,9 @@ async def ready(self) -> bool:
2929
Returns:
3030
Whether the receiver is still active.
3131
"""
32-
if self._closed:
32+
if self._ready_future.done():
3333
return False
34-
await asyncio.Future()
35-
return False
34+
return await self._ready_future
3635

3736
@override
3837
def consume(self) -> ReceiverMessageT_co: # noqa: DOC503 (raised indirectly)
@@ -47,11 +46,12 @@ def consume(self) -> ReceiverMessageT_co: # noqa: DOC503 (raised indirectly)
4746
ReceiverStoppedError: If the receiver stopped producing messages.
4847
ReceiverError: If there is some problem with the underlying receiver.
4948
"""
50-
if self._closed:
49+
if self._ready_future.done():
5150
raise ReceiverStoppedError(self)
5251
raise ReceiverError("`consume()` must be preceded by a call to `ready()`", self)
5352

5453
@override
5554
def close(self) -> None:
5655
"""Stop the receiver."""
57-
self._closed = True
56+
if not self._ready_future.done():
57+
self._ready_future.set_result(False)

tests/experimental/test_nop_receiver.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,28 @@ async def test_consuming_raises() -> None:
3838
receiver.close()
3939
with pytest.raises(ReceiverStoppedError):
4040
receiver.consume()
41+
42+
43+
async def test_close_method_effect_on_ready() -> None:
44+
"""Test `ready()` terminates when the receiver is closed.
45+
46+
When the receiver is closed, `ready()` should return False.
47+
This test verifies that:
48+
1. `ready()` returns False immediately after `close()` is called
49+
2. `ready()` and `close()` can be called multiple times
50+
"""
51+
receiver = NopReceiver[int]()
52+
53+
# Create a task that waits for the receiver to be ready.
54+
task = asyncio.create_task(receiver.ready())
55+
56+
# Wait for the task to start.
57+
await asyncio.sleep(0.1)
58+
59+
# Close the receiver and wait for the task to complete.
60+
receiver.close()
61+
assert await asyncio.wait_for(task, timeout=0.1) is False
62+
63+
# Second call to `close()` or `ready()` should not raise an error.
64+
receiver.close()
65+
assert await asyncio.wait_for(receiver.ready(), timeout=0.1) is False

0 commit comments

Comments
 (0)