From 7bd6ff374a4355def7b1f815f576106be2cee3f1 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 28 May 2020 14:25:36 +0200 Subject: [PATCH] Fix watchdog & scheduler (#1757) * Fix watchdog & scheduler * Update supervisor/misc/scheduler.py Co-authored-by: Martin Hjelmare * Fix callback * hmm Co-authored-by: Martin Hjelmare --- supervisor/bootstrap.py | 8 +++++--- supervisor/const.py | 1 + supervisor/core.py | 7 ++++++- supervisor/coresys.py | 19 ++++++++++++++----- supervisor/{ => misc}/hwmon.py | 4 ++-- supervisor/misc/scheduler.py | 19 ++++++++++--------- supervisor/{ => misc}/secrets.py | 4 ++-- supervisor/{ => misc}/tasks.py | 4 ++-- supervisor/snapshots/__init__.py | 18 +++++++++--------- 9 files changed, 51 insertions(+), 33 deletions(-) rename supervisor/{ => misc}/hwmon.py (96%) rename supervisor/{ => misc}/secrets.py (96%) rename supervisor/{ => misc}/tasks.py (99%) diff --git a/supervisor/bootstrap.py b/supervisor/bootstrap.py index 16af7b76c..102cbfb22 100644 --- a/supervisor/bootstrap.py +++ b/supervisor/bootstrap.py @@ -27,15 +27,16 @@ from .discovery import Discovery from .hassos import HassOS from .homeassistant import HomeAssistant from .host import HostManager -from .hwmon import HwMonitor from .ingress import Ingress +from .misc.hwmon import HwMonitor +from .misc.scheduler import Scheduler +from .misc.secrets import SecretsManager +from .misc.tasks import Tasks from .plugins import PluginManager -from .secrets import SecretsManager from .services import ServiceManager from .snapshots import SnapshotManager from .store import StoreManager from .supervisor import Supervisor -from .tasks import Tasks from .updater import Updater from .utils.dt import fetch_timezone @@ -70,6 +71,7 @@ async def initialize_coresys() -> None: coresys.dbus = DBusManager(coresys) coresys.hassos = HassOS(coresys) coresys.secrets = SecretsManager(coresys) + coresys.scheduler = Scheduler(coresys) # bootstrap config initialize_system_data(coresys) diff --git a/supervisor/const.py b/supervisor/const.py index e5e2f0b0f..c09bf55e6 100644 --- a/supervisor/const.py +++ b/supervisor/const.py @@ -362,6 +362,7 @@ class CoreStates(str, Enum): STARTUP = "startup" RUNNING = "running" FREEZE = "freeze" + STOPPING = "stopping" class LogLevel(str, Enum): diff --git a/supervisor/core.py b/supervisor/core.py index fa5a5ba56..aa6cda192 100644 --- a/supervisor/core.py +++ b/supervisor/core.py @@ -187,7 +187,7 @@ class Core(CoreSysAttributes): async def stop(self): """Stop a running orchestration.""" # don't process scheduler anymore - self.sys_scheduler.suspend = True + self.state = CoreStates.STOPPING # store new last boot / prevent time adjustments if self.state == CoreStates.RUNNING: @@ -213,12 +213,17 @@ class Core(CoreSysAttributes): async def shutdown(self): """Shutdown all running containers in correct order.""" + # don't process scheduler anymore + self.state = CoreStates.STOPPING + + # Shutdown Application Add-ons, using Home Assistant API await self.sys_addons.shutdown(STARTUP_APPLICATION) # Close Home Assistant with suppress(HassioError): await self.sys_homeassistant.stop() + # Shutdown System Add-ons await self.sys_addons.shutdown(STARTUP_SERVICES) await self.sys_addons.shutdown(STARTUP_SYSTEM) await self.sys_addons.shutdown(STARTUP_INITIALIZE) diff --git a/supervisor/coresys.py b/supervisor/coresys.py index 3cf49bd58..57c908dd0 100644 --- a/supervisor/coresys.py +++ b/supervisor/coresys.py @@ -10,7 +10,6 @@ from .config import CoreConfig from .const import UpdateChannels from .docker import DockerAPI from .misc.hardware import Hardware -from .misc.scheduler import Scheduler if TYPE_CHECKING: from .addons import AddonManager @@ -21,16 +20,17 @@ if TYPE_CHECKING: from .dbus import DBusManager from .discovery import Discovery from .hassos import HassOS - from .hwmon import HwMonitor + from .misc.scheduler import Scheduler + from .misc.hwmon import HwMonitor + from .misc.secrets import SecretsManager + from .misc.tasks import Tasks from .homeassistant import HomeAssistant from .host import HostManager from .ingress import Ingress - from .secrets import SecretsManager from .services import ServiceManager from .snapshots import SnapshotManager from .supervisor import Supervisor from .store import StoreManager - from .tasks import Tasks from .updater import Updater from .plugins import PluginManager @@ -58,7 +58,6 @@ class CoreSys: self._config: CoreConfig = CoreConfig() self._hardware: Hardware = Hardware() self._docker: DockerAPI = DockerAPI() - self._scheduler: Scheduler = Scheduler() # Internal objects pointers self._core: Optional[Core] = None @@ -77,6 +76,7 @@ class CoreSys: self._hassos: Optional[HassOS] = None self._services: Optional[ServiceManager] = None self._secrets: Optional[SecretsManager] = None + self._scheduler: Optional[Scheduler] = None self._store: Optional[StoreManager] = None self._discovery: Optional[Discovery] = None self._hwmonitor: Optional[HwMonitor] = None @@ -127,8 +127,17 @@ class CoreSys: @property def scheduler(self) -> Scheduler: """Return Scheduler object.""" + if self._scheduler is None: + raise RuntimeError("Scheduler not set!") return self._scheduler + @scheduler.setter + def scheduler(self, value: Scheduler) -> None: + """Set a Scheduler object.""" + if self._scheduler: + raise RuntimeError("Scheduler already set!") + self._scheduler = value + @property def core(self) -> Core: """Return core object.""" diff --git a/supervisor/hwmon.py b/supervisor/misc/hwmon.py similarity index 96% rename from supervisor/hwmon.py rename to supervisor/misc/hwmon.py index 395264258..2d6ebf469 100644 --- a/supervisor/hwmon.py +++ b/supervisor/misc/hwmon.py @@ -6,8 +6,8 @@ from typing import Optional import pyudev -from .coresys import CoreSys, CoreSysAttributes -from .utils import AsyncCallFilter +from ..coresys import CoreSys, CoreSysAttributes +from ..utils import AsyncCallFilter _LOGGER: logging.Logger = logging.getLogger(__name__) diff --git a/supervisor/misc/scheduler.py b/supervisor/misc/scheduler.py index 0fd422b2b..f5a7af332 100644 --- a/supervisor/misc/scheduler.py +++ b/supervisor/misc/scheduler.py @@ -1,8 +1,10 @@ """Schedule for Supervisor.""" -import asyncio from datetime import date, datetime, time, timedelta import logging +from ..const import CoreStates +from ..coresys import CoreSys, CoreSysAttributes + _LOGGER: logging.Logger = logging.getLogger(__name__) INTERVAL = "interval" @@ -11,14 +13,13 @@ CALL = "callback" TASK = "task" -class Scheduler: +class Scheduler(CoreSysAttributes): """Schedule task inside Supervisor.""" - def __init__(self): + def __init__(self, coresys: CoreSys): """Initialize task schedule.""" - self.loop = asyncio.get_running_loop() + self.coresys: CoreSys = coresys self._data = {} - self.suspend = False def register_task(self, coro_callback, interval, repeat=True): """Schedule a coroutine. @@ -40,8 +41,8 @@ class Scheduler: """Run a scheduled task.""" data = self._data[task_id] - if not self.suspend: - self.loop.create_task(data[CALL]()) + if self.sys_core.state == CoreStates.RUNNING: + self.sys_create_task(data[CALL]()) if data[REPEAT]: self._schedule_task(data[INTERVAL], task_id) @@ -51,7 +52,7 @@ class Scheduler: def _schedule_task(self, interval, task_id): """Schedule a task on loop.""" if isinstance(interval, (int, float)): - job = self.loop.call_later(interval, self._run_task, task_id) + job = self.sys_loop.call_later(interval, self._run_task, task_id) elif isinstance(interval, time): today = datetime.combine(date.today(), interval) tomorrow = datetime.combine(date.today() + timedelta(days=1), interval) @@ -62,7 +63,7 @@ class Scheduler: else: calc = tomorrow - job = self.loop.call_at(calc.timestamp(), self._run_task, task_id) + job = self.sys_loop.call_at(calc.timestamp(), self._run_task, task_id) else: _LOGGER.critical( "Unknown interval %s (type: %s) for scheduler %s", diff --git a/supervisor/secrets.py b/supervisor/misc/secrets.py similarity index 96% rename from supervisor/secrets.py rename to supervisor/misc/secrets.py index 933d6126a..b37953907 100644 --- a/supervisor/secrets.py +++ b/supervisor/misc/secrets.py @@ -6,8 +6,8 @@ from typing import Dict, Optional, Union from ruamel.yaml import YAML, YAMLError -from .coresys import CoreSys, CoreSysAttributes -from .utils import AsyncThrottle +from ..coresys import CoreSys, CoreSysAttributes +from ..utils import AsyncThrottle _LOGGER: logging.Logger = logging.getLogger(__name__) diff --git a/supervisor/tasks.py b/supervisor/misc/tasks.py similarity index 99% rename from supervisor/tasks.py rename to supervisor/misc/tasks.py index c146c8c39..412c52ab7 100644 --- a/supervisor/tasks.py +++ b/supervisor/misc/tasks.py @@ -2,8 +2,8 @@ import asyncio import logging -from .coresys import CoreSysAttributes -from .exceptions import ( +from ..coresys import CoreSysAttributes +from ..exceptions import ( AudioError, CliError, CoreDNSError, diff --git a/supervisor/snapshots/__init__.py b/supervisor/snapshots/__init__.py index 6746b408c..658b59848 100644 --- a/supervisor/snapshots/__init__.py +++ b/supervisor/snapshots/__init__.py @@ -3,7 +3,7 @@ import asyncio import logging from pathlib import Path -from ..const import FOLDER_HOMEASSISTANT, SNAPSHOT_FULL, SNAPSHOT_PARTIAL +from ..const import FOLDER_HOMEASSISTANT, SNAPSHOT_FULL, SNAPSHOT_PARTIAL, CoreStates from ..coresys import CoreSysAttributes from ..utils.dt import utcnow from .snapshot import Snapshot @@ -125,7 +125,7 @@ class SnapshotManager(CoreSysAttributes): snapshot = self._create_snapshot(name, SNAPSHOT_FULL, password) _LOGGER.info("Full-Snapshot %s start", snapshot.slug) try: - self.sys_scheduler.suspend = True + self.sys_core.state = CoreStates.FREEZE await self.lock.acquire() async with snapshot: @@ -147,7 +147,7 @@ class SnapshotManager(CoreSysAttributes): return snapshot finally: - self.sys_scheduler.suspend = False + self.sys_core.state = CoreStates.RUNNING self.lock.release() async def do_snapshot_partial( @@ -164,7 +164,7 @@ class SnapshotManager(CoreSysAttributes): _LOGGER.info("Partial-Snapshot %s start", snapshot.slug) try: - self.sys_scheduler.suspend = True + self.sys_core.state = CoreStates.FREEZE await self.lock.acquire() async with snapshot: @@ -196,7 +196,7 @@ class SnapshotManager(CoreSysAttributes): return snapshot finally: - self.sys_scheduler.suspend = False + self.sys_core.state = CoreStates.RUNNING self.lock.release() async def do_restore_full(self, snapshot, password=None): @@ -215,7 +215,7 @@ class SnapshotManager(CoreSysAttributes): _LOGGER.info("Full-Restore %s start", snapshot.slug) try: - self.sys_scheduler.suspend = True + self.sys_core.state = CoreStates.FREEZE await self.lock.acquire() async with snapshot: @@ -267,7 +267,7 @@ class SnapshotManager(CoreSysAttributes): return True finally: - self.sys_scheduler.suspend = False + self.sys_core.state = CoreStates.RUNNING self.lock.release() async def do_restore_partial( @@ -287,7 +287,7 @@ class SnapshotManager(CoreSysAttributes): _LOGGER.info("Partial-Restore %s start", snapshot.slug) try: - self.sys_scheduler.suspend = True + self.sys_core.state = CoreStates.FREEZE await self.lock.acquire() async with snapshot: @@ -339,5 +339,5 @@ class SnapshotManager(CoreSysAttributes): return True finally: - self.sys_scheduler.suspend = False + self.sys_core.state = CoreStates.RUNNING self.lock.release()