Periodic Timers
Xronos offers several ways to implement timed behavior, and the easiest place to
start is with periodic timers. To follow along, copy the example below into a
new file called timer.py.
import datetime
import xronos
MAX_COUNT = 5
class Timed(xronos.Reactor):
_timer = xronos.PeriodicTimerDeclaration()
def __init__(self, period):
super().__init__()
self._timer.period = period
self._count = 0
@xronos.reaction
def hello(self, interface):
interface.add_trigger(self.startup)
return lambda: print(f"{self.get_time()}: {self.name} says 'Hello!'")
@xronos.reaction
def on_timer(self, interface):
interface.add_trigger(self._timer)
shutdown_effect = interface.add_effect(self.shutdown)
def handler():
self._count += 1
print(
f"{self.get_time()}: {self.name}'s timer triggered "
f"(count={self._count})"
)
if self._count == MAX_COUNT:
shutdown_effect.trigger_shutdown()
return handler
@xronos.reaction
def goodbye(self, interface):
interface.add_trigger(self.shutdown)
return lambda: print(f"{self.get_time()}: {self.name} says 'Goodbye!'")
def main():
env = xronos.Environment()
env.create_reactor("timed", Timed, period=datetime.timedelta(seconds=1))
env.execute()
if __name__ == "__main__":
main()
import datetime
from typing import Callable
import xronos
MAX_COUNT = 5
class Timed(xronos.Reactor):
_timer = xronos.PeriodicTimerDeclaration()
def __init__(self, period: datetime.timedelta) -> None:
super().__init__()
self._timer.period = period
self._count = 0
@xronos.reaction
def hello(self, interface: xronos.ReactionInterface) -> Callable[[], None]:
interface.add_trigger(self.startup)
return lambda: print(f"{self.get_time()}: {self.name} says 'Hello!'")
@xronos.reaction
def on_timer(self, interface: xronos.ReactionInterface) -> Callable[[], None]:
interface.add_trigger(self._timer)
shutdown_effect = interface.add_effect(self.shutdown)
def handler() -> None:
self._count += 1
print(
f"{self.get_time()}: {self.name}'s timer triggered "
f"(count={self._count})"
)
if self._count == MAX_COUNT:
shutdown_effect.trigger_shutdown()
return handler
@xronos.reaction
def goodbye(self, interface: xronos.ReactionInterface) -> Callable[[], None]:
interface.add_trigger(self.shutdown)
return lambda: print(f"{self.get_time()}: {self.name} says 'Goodbye!'")
def main() -> None:
env = xronos.Environment()
env.create_reactor("timed", Timed, period=datetime.timedelta(seconds=1))
env.execute()
if __name__ == "__main__":
main()
To create a periodic timer, instantiate
xronos.PeriodicTimerDeclaration as a class attribute. This declares a
PeriodicTimer object that the Xronos runtime initializes for you
automatically. You can then interact with the timer object using self._timer.
The __init__ method of Timed configures the timer’s period from an argument
it receives, and also initializes the state variable self._count to 0.
The Timed reactor defines three reactions. The hello reaction will feel
familiar from the “Hello, World!” example — it prints the current time, the
reactor’s name, and ‘Hello!’. The goodbye reaction produces similar output in
response to the builtin shutdown event.
The on_timer reaction declares the timer as its trigger, and also defines an
effect — specifically a ShutdownEffect that can be used to
stop the program. Its handler is a little more involved than the simple lambda
we’ve seen so far: it defines a nested handler function and returns it.
def handler():
self._count += 1
print(
f"{self.get_time()}: {self.name}'s timer triggered "
f"(count={self._count})"
)
if self._count == MAX_COUNT:
shutdown_effect.trigger_shutdown()
return handler
The handler increments self._count, prints the current time, the timer’s name,
and the current count, and then checks whether the count has reached MAX_COUNT.
When it has, trigger_shutdown() is called to stop
the program.
The program runs for about 4 seconds and produces output similar to the following:
$ python timer.py
2024-11-29 10:17:13.893071: timed says 'Hello!'
2024-11-29 10:17:13.893071: timed's timer triggered (count=1)
2024-11-29 10:17:14.893071: timed's timer triggered (count=2)
2024-11-29 10:17:15.893071: timed's timer triggered (count=3)
2024-11-29 10:17:16.893071: timed's timer triggered (count=4)
2024-11-29 10:17:17.893071: timed's timer triggered (count=5)
2024-11-29 10:17:17.893071: timed says 'Goodbye!'
Take a close look at the printed timestamps. The timestamp from hello matches
the first line printed by on_timer. That’s because we didn’t configure an
offset, so the first timer event fires immediately
at startup — simultaneous with the startup event. You’ll
also notice that the individual timer events are spaced exactly one second apart.
How does Xronos achieve that kind of precision? Time in the Xronos SDK works
differently from the wall-clock time most programs use. The runtime manages its
own internal clock, and that clock is central to how program execution is
controlled. One key property: the time a reaction observes is always equal to
the timestamp of the event that triggered it. As a result, any two calls to
get_time() within the same reaction handler will return
the same value.
This synchrony of timestamps carries across multiple reactors as well. Try
modifying the instantiation code as shown below, so the program runs three
Timed reactors with periods of 1 second, 2 seconds, and 3 seconds.
def main():
env = xronos.Environment()
env.create_reactor("timed1", Timed, period=datetime.timedelta(seconds=1))
env.create_reactor("timed2", Timed, period=datetime.timedelta(seconds=2))
env.create_reactor("timed3", Timed, period=datetime.timedelta(seconds=3))
env.execute()
def main():
env = xronos.Environment()
env.create_reactor("timed1", Timed, period=datetime.timedelta(seconds=1))
env.create_reactor("timed2", Timed, period=datetime.timedelta(seconds=2))
env.create_reactor("timed3", Timed, period=datetime.timedelta(seconds=3))
env.execute()
Running the modified program produces output similar to the following:
$ python timer.py
2024-11-29 10:48:57.423695: timed2 says 'Hello!'
2024-11-29 10:48:57.423695: timed3 says 'Hello!'
2024-11-29 10:48:57.423695: timed1 says 'Hello!'
2024-11-29 10:48:57.423695: timed2's timer triggered (count=1)
2024-11-29 10:48:57.423695: timed3's timer triggered (count=1)
2024-11-29 10:48:57.423695: timed1's timer triggered (count=1)
2024-11-29 10:48:58.423695: timed1's timer triggered (count=2)
2024-11-29 10:48:59.423695: timed2's timer triggered (count=2)
2024-11-29 10:48:59.423695: timed1's timer triggered (count=3)
2024-11-29 10:49:00.423695: timed3's timer triggered (count=2)
2024-11-29 10:49:00.423695: timed1's timer triggered (count=4)
2024-11-29 10:49:01.423695: timed2's timer triggered (count=3)
2024-11-29 10:49:01.423695: timed1's timer triggered (count=5)
2024-11-29 10:49:01.423695: timed2 says 'Goodbye!'
2024-11-29 10:49:01.423695: timed3 says 'Goodbye!'
2024-11-29 10:49:01.423695: timed1 says 'Goodbye!'
Notice how all three reactors say ‘Hello!’ or ‘Goodbye!’ at the same timestamp, and how the timestamps of their individual timer events align with each other.

