Skip to content

Commit f49f110

Browse files
committed
Timer: Add support for rearming timer with new interval
Signed-off-by: Mathias L. Baumann <[email protected]>
1 parent 62c2248 commit f49f110

File tree

2 files changed

+30
-2
lines changed

2 files changed

+30
-2
lines changed

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Frequenz channels Release Notes
22

3+
## New Features
4+
5+
- `Timer.reset()` now supports setting the interval and will restart the timer with the new interval.
6+
37
## Bug Fixes
48

59
- `FileWatcher`: Fixed `ready()` method to return False when an error occurs. Before this fix, `select()` (and other code using `ready()`) never detected the `FileWatcher` was stopped and the `select()` loop was continuously waking up to inform the receiver was ready.

src/frequenz/channels/timer.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,8 @@ def __init__( # pylint: disable=too-many-arguments
523523
See the documentation of `MissedTickPolicy` for details.
524524
"""
525525

526+
self._reset_event = asyncio.Event()
527+
526528
self._loop: asyncio.AbstractEventLoop = (
527529
loop if loop is not None else asyncio.get_running_loop()
528530
)
@@ -584,7 +586,12 @@ def is_running(self) -> bool:
584586
"""Whether the timer is running."""
585587
return not self._stopped
586588

587-
def reset(self, *, start_delay: timedelta = timedelta(0)) -> None:
589+
def reset(
590+
self,
591+
*,
592+
interval: timedelta | None = None,
593+
start_delay: timedelta = timedelta(0),
594+
) -> None:
588595
"""Reset the timer to start timing from now (plus an optional delay).
589596
590597
If the timer was stopped, or not started yet, it will be started.
@@ -593,6 +600,8 @@ def reset(self, *, start_delay: timedelta = timedelta(0)) -> None:
593600
more details.
594601
595602
Args:
603+
interval: The new interval between ticks. If `None`, the current
604+
interval is kept.
596605
start_delay: The delay before the timer should start. This has microseconds
597606
resolution, anything smaller than a microsecond means no delay.
598607
@@ -605,7 +614,16 @@ def reset(self, *, start_delay: timedelta = timedelta(0)) -> None:
605614
if start_delay_ms < 0:
606615
raise ValueError(f"`start_delay` can't be negative, got {start_delay}")
607616
self._stopped = False
617+
618+
if interval is not None:
619+
self._interval = _to_microseconds(interval)
620+
621+
prev_next_tick_time = self._next_tick_time
608622
self._next_tick_time = self._now() + start_delay_ms + self._interval
623+
624+
if prev_next_tick_time and prev_next_tick_time > self._next_tick_time:
625+
self._reset_event.set()
626+
609627
self._current_drift = None
610628

611629
def stop(self) -> None:
@@ -664,7 +682,13 @@ async def ready(self) -> bool: # noqa: DOC502
664682
# could be reset while we are sleeping, in which case we need to recalculate
665683
# the time to the next tick and try again.
666684
while time_to_next_tick > 0:
667-
await asyncio.sleep(time_to_next_tick / 1_000_000)
685+
await asyncio.wait(
686+
[
687+
asyncio.create_task(asyncio.sleep(time_to_next_tick / 1_000_000)),
688+
asyncio.create_task(self._reset_event.wait()),
689+
],
690+
return_when=asyncio.FIRST_COMPLETED,
691+
)
668692
now = self._now()
669693
time_to_next_tick = self._next_tick_time - now
670694

0 commit comments

Comments
 (0)