diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b3069d60..e42ff57f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -145,4 +145,4 @@ ## Bug Fixes - +* `Timer`: Fix bug that was causing calls to `reset()` to not reset the timer, if the timer was already being awaited. diff --git a/src/frequenz/channels/timer.py b/src/frequenz/channels/timer.py index 18db2beb..229a8d9c 100644 --- a/src/frequenz/channels/timer.py +++ b/src/frequenz/channels/timer.py @@ -696,10 +696,15 @@ async def ready(self) -> bool: # noqa: DOC502 now = self._now() time_to_next_tick = self._next_tick_time - now + # If we didn't reach the tick yet, sleep until we do. - if time_to_next_tick > 0: + # We need to do this in a loop also reacting to the reset event, as the timer + # could be reset while we are sleeping, in which case we need to recalculated + # the time to the next tick and try again. + while time_to_next_tick > 0: await asyncio.sleep(time_to_next_tick / 1_000_000) now = self._now() + time_to_next_tick = self._next_tick_time - now # If a stop was explicitly requested during the sleep, we bail out. if self._stopped: diff --git a/tests/test_timer_integration.py b/tests/test_timer_integration.py new file mode 100644 index 00000000..7132ddc9 --- /dev/null +++ b/tests/test_timer_integration.py @@ -0,0 +1,34 @@ +# License: MIT +# Copyright © 2023 Frequenz Energy-as-a-Service GmbH + +"""Integration tests for the timer.""" + + +import asyncio +from datetime import timedelta + +import async_solipsism +import pytest + +from frequenz.channels.timer import Timer + + +@pytest.mark.integration +async def test_timer_timeout_reset( + event_loop: async_solipsism.EventLoop, # pylint: disable=redefined-outer-name +) -> None: + """Test that the receiving is properly adjusted after a reset.""" + + async def timer_wait(timer: Timer) -> None: + await timer.receive() + + async with asyncio.timeout(2.0): + async with asyncio.TaskGroup() as task_group: + timer = Timer.timeout(timedelta(seconds=1.0)) + start_time = event_loop.time() + task_group.create_task(timer_wait(timer)) + await asyncio.sleep(0.5) + timer.reset() + + run_time = event_loop.time() - start_time + assert run_time >= 1.5