55
66# Quick Start
77
8+ Info: Important
9+ This quick start is provided to have a quick feeling of how to use this module, but
10+ it is extremely important to understand how timers behave when they are delayed.
11+
12+ We recommend emphatically to read about [missed ticks and
13+ drifting](#missed-ticks-and-drifting) before using timers in production.
14+
815If you need to do something as periodically as possible (avoiding
9- [drifts](#missed-ticks-and-drifting)), you can use use
10- a [`periodic() `][frequenz.channels.timer.Timer.periodic] timer.
16+ [drifts](#missed-ticks-and-drifting)), you can use
17+ a [`Timer `][frequenz.channels.timer.Timer] like this:
1118
12- Example: Periodic Timer
19+ Example: Periodic Timer Example
1320 ```python
1421 import asyncio
1522 from datetime import datetime, timedelta
1825
1926
2027 async def main() -> None:
21- async for drift in Timer.periodic (timedelta(seconds=1.0)):
28+ async for drift in Timer(timedelta(seconds=1.0), TriggerAllMissed( )):
2229 print(f"The timer has triggered at {datetime.now()} with a drift of {drift}")
2330
2431
2532 asyncio.run(main())
2633 ```
2734
35+ This timer will tick as close as every second as possible, even if the loop is busy
36+ doing something else for a good amount of time. In extreme cases, if the loop was
37+ busy for a few seconds, the timer will trigger a few times in a row to catch up, one
38+ for every missed tick.
39+
2840If, instead, you need a timeout, for example to abort waiting for other receivers after
29- a certain amount of time, you can use
30- a [`timeout()`][frequenz.channels.timer.Timer.timeout] timer.
41+ a certain amount of time, you can use a [`Timer`][frequenz.channels.timer.Timer] like
42+ this:
3143
32- Example: Timeout
44+ Example: Timeout Example
3345 ```python
3446 import asyncio
3547 from datetime import timedelta
@@ -42,7 +54,7 @@ async def main() -> None:
4254 channel = Anycast[int](name="data-channel")
4355 data_receiver = channel.new_receiver()
4456
45- timer = Timer.timeout (timedelta(seconds=1.0))
57+ timer = Timer(timedelta(seconds=1.0), SkipMissedAndDrift( ))
4658
4759 async for selected in select(data_receiver, timer):
4860 if selected_from(selected, data_receiver):
@@ -57,13 +69,10 @@ async def main() -> None:
5769 asyncio.run(main())
5870 ```
5971
60- This timer will *rearm* itself automatically after it was triggered, so it will trigger
61- again after the selected interval, no matter what the current drift was.
62-
63- Tip:
64- It is extremely important to understand how timers behave when they are
65- delayed, we recommned emphatically to read about [missed ticks and
66- drifting](#missed-ticks-and-drifting) before using timers in production.
72+ This timer will *rearm* itself automatically after it was triggered, so it will
73+ trigger again after the selected interval, no matter what the current drift was. So
74+ if the loop was busy for a few seconds, the timer will trigger immediately and then
75+ wait for another second before triggering again. The missed ticks are skipped.
6776
6877# Missed Ticks And Drifting
6978
@@ -472,14 +481,6 @@ class Timer(Receiver[timedelta]):
472481 [`missed_tick_policy`][frequenz.channels.timer.Timer.missed_tick_policy]. Missing
473482 ticks might or might not trigger a message and the drift could be accumulated or not
474483 depending on the chosen policy.
475-
476- For the most common cases, a specialized constructor is provided:
477-
478- * [`periodic()`][frequenz.channels.timer.Timer.periodic]:
479- {{docstring_summary("frequenz.channels.timer.Timer.periodic")}}
480-
481- * [`timeout()`][frequenz.channels.timer.Timer.timeout]:
482- {{docstring_summary("frequenz.channels.timer.Timer.timeout")}}
483484 """
484485
485486 def __init__ ( # pylint: disable=too-many-arguments
@@ -494,7 +495,7 @@ def __init__( # pylint: disable=too-many-arguments
494495 ) -> None :
495496 """Create an instance.
496497
497- See the class documentation for details.
498+ See the [ class documentation][frequenz.channels.timer.Timer] for details.
498499
499500 Args:
500501 interval: The time between timer ticks. Must be at least
@@ -580,175 +581,6 @@ def __init__( # pylint: disable=too-many-arguments
580581 if auto_start :
581582 self .reset (start_delay = start_delay )
582583
583- # We need a noqa here because the docs have a Raises section but the documented
584- # exceptions are raised indirectly.
585- @classmethod
586- def timeout ( # noqa: DOC502
587- cls ,
588- delay : timedelta ,
589- / ,
590- * ,
591- auto_start : bool = True ,
592- start_delay : timedelta = timedelta (0 ),
593- loop : asyncio .AbstractEventLoop | None = None ,
594- ) -> Timer :
595- """Create a timer useful for tracking timeouts.
596-
597- A [timeout][frequenz.channels.timer.Timer.timeout] is
598- a [`Timer`][frequenz.channels.timer.Timer] that
599- [resets][frequenz.channels.timer.Timer.reset] automatically after it triggers,
600- so it will trigger again after the selected interval, no matter what the current
601- drift was. This means timeout timers will accumulate drift.
602-
603- Tip:
604- Timeouts are a shortcut to create
605- a [`Timer`][frequenz.channels.timer.Timer] with the
606- [`SkipMissedAndDrift`][frequenz.channels.timer.SkipMissedAndDrift] policy.
607-
608- Example: Timeout example
609- ```python
610- import asyncio
611- from datetime import timedelta
612-
613- from frequenz.channels import Anycast, select, selected_from
614- from frequenz.channels.timer import Timer
615-
616-
617- async def main() -> None:
618- channel = Anycast[int](name="data-channel")
619- data_receiver = channel.new_receiver()
620-
621- timer = Timer.timeout(timedelta(seconds=1.0))
622-
623- async for selected in select(data_receiver, timer):
624- if selected_from(selected, data_receiver):
625- print(f"Received data: {selected.value}")
626- elif selected_from(selected, timer):
627- drift = selected.value
628- print(f"No data received for {timer.interval + drift} seconds, giving up")
629- break
630-
631-
632- asyncio.run(main())
633- ```
634-
635- Args:
636- delay: The time until the timer ticks. Must be at least
637- 1 microsecond.
638- auto_start: Whether the timer should be started when the
639- instance is created. This can only be `True` if there is
640- already a running loop or an explicit `loop` that is running
641- was passed.
642- start_delay: The delay before the timer should start. If `auto_start` is
643- `False`, an exception is raised. This has microseconds resolution,
644- anything smaller than a microsecond means no delay.
645- loop: The event loop to use to track time. If `None`,
646- `asyncio.get_running_loop()` will be used.
647-
648- Returns:
649- The timer instance.
650-
651- Raises:
652- RuntimeError: if it was called without a loop and there is no
653- running loop.
654- ValueError: if `interval` is not positive or is smaller than 1
655- microsecond; if `start_delay` is negative or `start_delay` was specified
656- but `auto_start` is `False`.
657- """
658- return Timer (
659- delay ,
660- SkipMissedAndDrift (delay_tolerance = timedelta (0 )),
661- auto_start = auto_start ,
662- start_delay = start_delay ,
663- loop = loop ,
664- )
665-
666- # We need a noqa here because the docs have a Raises section but the documented
667- # exceptions are raised indirectly.
668- @classmethod
669- def periodic ( # noqa: DOC502 pylint: disable=too-many-arguments
670- cls ,
671- period : timedelta ,
672- / ,
673- * ,
674- skip_missed_ticks : bool = False ,
675- auto_start : bool = True ,
676- start_delay : timedelta = timedelta (0 ),
677- loop : asyncio .AbstractEventLoop | None = None ,
678- ) -> Timer :
679- """Create a periodic timer.
680-
681- A [periodic timer][frequenz.channels.timer.Timer.periodic] is
682- a [`Timer`][frequenz.channels.timer.Timer] that tries as hard as possible to
683- trigger at regular intervals. This means that if the timer is delayed for any
684- reason, it will trigger immediately and then try to catch up with the original
685- schedule.
686-
687- Optionally, a periodic timer can be configured to skip missed ticks and re-sync
688- with the original schedule (`skip_missed_ticks` argument). This could be useful
689- if you want the timer is as periodic as possible but if there are big delays you
690- don't end up with big bursts.
691-
692- Tip:
693- Periodic timers are a shortcut to create
694- a [`Timer`][frequenz.channels.timer.Timer] with either the
695- [`TriggerAllMissed`][frequenz.channels.timer.TriggerAllMissed] policy (when
696- `skip_missed_ticks` is `False`) or
697- [`SkipMissedAndResync`][frequenz.channels.timer.SkipMissedAndResync]
698- otherwise.
699-
700- Example:
701- ```python
702- import asyncio
703- from datetime import datetime, timedelta
704-
705- from frequenz.channels.timer import Timer
706-
707-
708- async def main() -> None:
709- async for drift in Timer.periodic(timedelta(seconds=1.0)):
710- print(f"The timer has triggered at {datetime.now()} with a drift of {drift}")
711-
712-
713- asyncio.run(main())
714- ```
715-
716- Args:
717- period: The time between timer ticks. Must be at least
718- 1 microsecond.
719- skip_missed_ticks: Whether to skip missed ticks or trigger them
720- all until it catches up.
721- auto_start: Whether the timer should be started when the
722- instance is created. This can only be `True` if there is
723- already a running loop or an explicit `loop` that is running
724- was passed.
725- start_delay: The delay before the timer should start. If `auto_start` is
726- `False`, an exception is raised. This has microseconds resolution,
727- anything smaller than a microsecond means no delay.
728- loop: The event loop to use to track time. If `None`,
729- `asyncio.get_running_loop()` will be used.
730-
731- Returns:
732- The timer instance.
733-
734- Raises:
735- RuntimeError: if it was called without a loop and there is no
736- running loop.
737- ValueError: if `interval` is not positive or is smaller than 1
738- microsecond; if `start_delay` is negative or `start_delay` was specified
739- but `auto_start` is `False`.
740- """
741- missed_tick_policy = (
742- SkipMissedAndResync () if skip_missed_ticks else TriggerAllMissed ()
743- )
744- return Timer (
745- period ,
746- missed_tick_policy ,
747- auto_start = auto_start ,
748- start_delay = start_delay ,
749- loop = loop ,
750- )
751-
752584 @property
753585 def interval (self ) -> timedelta :
754586 """The interval between timer ticks.
0 commit comments