Skip to content

Commit

Permalink
fix:events_in_the_past (#149)
Browse files Browse the repository at this point in the history
* fix:events_in_the_past

don't allow event scheduler to schedule events until clock has been synced since first boot

some systems will think we are still in the last century at boot time!

the system.clock.synced event needs to be emitted externally by the OS itself, eg OpenVoiceOS/raspOVOS@3ab5f44#diff-390ab68cb47cd597ab61ab48741f9d35ce7a7d514a4a40014df1222d402bead8R8

* fix:events_in_the_past

don't allow event scheduler to schedule events until clock has been synced since first boot

some systems will think we are still in the last century at boot time!

the system.clock.synced event needs to be emitted externally by the OS itself, eg OpenVoiceOS/raspOVOS@3ab5f44#diff-390ab68cb47cd597ab61ab48741f9d35ce7a7d514a4a40014df1222d402bead8R8

* Update scheduler.py

* Update requirements.txt

* Update scheduler.py

* Update scheduler.py

* Update scheduler.py
  • Loading branch information
JarbasAl authored Dec 5, 2024
1 parent 02549bd commit 478cfe0
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 17 deletions.
56 changes: 39 additions & 17 deletions ovos_bus_client/util/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@
A scheduled message will be kept and not sent until the system clock time
criteria is met.
"""

import datetime
import json
import os
import shutil
import time

from typing import Optional
from threading import Event
from os.path import isfile, join, expanduser
from threading import Event
from threading import Thread, Lock
from typing import Optional

from ovos_config.config import Configuration
from ovos_config.locations import get_xdg_data_save_path, get_xdg_config_save_path
from ovos_utils.log import LOG
from ovos_utils.events import EventContainer, EventSchedulerInterface

from ovos_bus_client.message import Message


Expand Down Expand Up @@ -65,12 +65,19 @@ class EventScheduler(Thread):
autostart: if True, start scheduler on init
"""

def __init__(self, bus,
schedule_file: str = 'schedule.json', autostart: bool = True):
def __init__(self, bus, schedule_file: str = 'schedule.json', autostart: bool = True):
super().__init__()

self.events = {}
self.event_lock = Lock()

# to check if its our first connection to the internet via clock_skew
self._last_sync = time.time()
self._dropped_events = 0
self._past_date = datetime.datetime(day=1, month=12, year=2024)
# Convert Unix timestamp to human-readable datetime
pretty_last_sync = datetime.datetime.fromtimestamp(self._last_sync).strftime("%Y-%m-%d %H:%M:%S")
LOG.debug(f"Boot time clock: {pretty_last_sync}")

self.bus = bus

Expand All @@ -85,14 +92,11 @@ def __init__(self, bus,
if self.schedule_file:
self.load()

self.bus.on('mycroft.scheduler.schedule_event',
self.schedule_event_handler)
self.bus.on('mycroft.scheduler.remove_event',
self.remove_event_handler)
self.bus.on('mycroft.scheduler.update_event',
self.update_event_handler)
self.bus.on('mycroft.scheduler.get_event',
self.get_event_handler)
self.bus.on('mycroft.scheduler.schedule_event', self.schedule_event_handler)
self.bus.on('mycroft.scheduler.remove_event', self.remove_event_handler)
self.bus.on('mycroft.scheduler.update_event', self.update_event_handler)
self.bus.on('mycroft.scheduler.get_event', self.get_event_handler)
self.bus.on('system.clock.synced', self.handle_system_clock_sync) # emitted by raspOVOS

self._running = Event()
self._stopping = Event()
Expand Down Expand Up @@ -201,6 +205,15 @@ def schedule_event(self, event: str, sched_time: float,
context to send when the
handler is called
"""
if datetime.datetime.fromtimestamp(self._last_sync) < self._past_date:
# this works around problems in raspOVOS images and other
# systems without RTC that didnt sync clock with the internet yet
# eg. issue demonstration without this:
# date time skill schedulling the hour change sound N times (+1hour every time until present)
LOG.error("Refusing to schedule event, system clock is in the past!")
self._dropped_events += 1
return

data = data or {}
with self.event_lock:
# get current list of scheduled times for event, [] if missing
Expand Down Expand Up @@ -238,7 +251,7 @@ def schedule_event_handler(self, message: Message):
if event and sched_time:
self.schedule_event(event, sched_time, repeat, data, context)
elif not event:
LOG.error('Scheduled event name not provided')
LOG.error('Scheduled event msg_type not provided')
else:
LOG.error('Scheduled event time not provided')

Expand All @@ -253,6 +266,16 @@ def remove_event(self, event: str):
if event in self.events:
self.events.pop(event)

def handle_system_clock_sync(self, message: Message):
# clock sync, are we in the past?
if datetime.datetime.fromtimestamp(self._last_sync) < self._past_date:
LOG.warning(f"Clock was in the past!!! {self._dropped_events} scheduled events have been dropped")

self._last_sync = time.time()
# Convert Unix timestamp to human-readable datetime
pretty_last_sync = datetime.datetime.fromtimestamp(self._last_sync).strftime("%Y-%m-%d %H:%M:%S")
LOG.info(f"clock sync: {pretty_last_sync}")

def remove_event_handler(self, message: Message):
"""
Messagebus interface to the remove_event method.
Expand Down Expand Up @@ -347,4 +370,3 @@ def shutdown(self):
if not isinstance(e, OSError):
self.store()
raise e

1 change: 1 addition & 0 deletions test/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pytest
pytest-cov
langcodes

0 comments on commit 478cfe0

Please sign in to comment.