Files
frigate/frigate/watchdog.py
T

144 lines
4.5 KiB
Python
Raw Normal View History

2020-11-01 15:45:04 -06:00
import datetime
2020-11-03 21:26:39 -06:00
import logging
2020-11-01 15:45:04 -06:00
import threading
2020-11-04 06:31:25 -06:00
import time
2026-03-20 19:02:47 -05:00
from collections import deque
2026-07-06 09:28:02 -08:00
from collections.abc import Callable
2026-03-20 19:02:47 -05:00
from dataclasses import dataclass, field
2023-05-29 12:31:17 +02:00
from multiprocessing.synchronize import Event as MpEvent
2020-11-01 15:45:04 -06:00
from frigate.notices import raise_notice
from frigate.object_detection.base import ObjectDetectProcess
2026-03-20 19:02:47 -05:00
from frigate.util.process import FrigateProcess
2023-07-06 08:28:50 -06:00
from frigate.util.services import restart_frigate
2021-09-18 07:40:27 -05:00
2020-11-03 21:26:39 -06:00
logger = logging.getLogger(__name__)
2026-03-20 19:02:47 -05:00
MAX_RESTARTS = 5
RESTART_WINDOW_S = 60
@dataclass
class MonitoredProcess:
"""A process monitored by the watchdog for automatic restart."""
name: str
process: FrigateProcess
factory: Callable[[], FrigateProcess]
on_restart: Callable[[FrigateProcess], None] | None = None
restart_timestamps: deque[float] = field(
default_factory=lambda: deque(maxlen=MAX_RESTARTS)
)
2026-04-29 17:20:19 -05:00
clean_exit_logged: bool = False
2026-03-20 19:02:47 -05:00
def is_restarting_too_fast(self, now: float) -> bool:
while (
self.restart_timestamps
and now - self.restart_timestamps[0] > RESTART_WINDOW_S
):
self.restart_timestamps.popleft()
return len(self.restart_timestamps) >= MAX_RESTARTS
2021-02-17 07:23:32 -06:00
2020-11-01 15:45:04 -06:00
class FrigateWatchdog(threading.Thread):
2026-03-20 19:02:47 -05:00
def __init__(
self,
detectors: dict[str, ObjectDetectProcess],
stop_event: MpEvent,
):
super().__init__(name="frigate_watchdog")
2020-11-01 15:45:04 -06:00
self.detectors = detectors
self.stop_event = stop_event
2026-03-20 19:02:47 -05:00
self._monitored: list[MonitoredProcess] = []
def register(
self,
name: str,
process: FrigateProcess,
factory: Callable[[], FrigateProcess],
on_restart: Callable[[FrigateProcess], None] | None = None,
) -> None:
"""Register a FrigateProcess for monitoring and automatic restart."""
self._monitored.append(
MonitoredProcess(
name=name,
process=process,
factory=factory,
on_restart=on_restart,
)
)
def _check_process(self, entry: MonitoredProcess) -> None:
if entry.process.is_alive():
return
exitcode = entry.process.exitcode
if exitcode == 0:
2026-04-29 17:20:19 -05:00
if not entry.clean_exit_logged:
logger.info("Process %s exited cleanly, not restarting", entry.name)
entry.clean_exit_logged = True
2026-03-20 19:02:47 -05:00
return
logger.warning(
"Process %s (PID %s) exited with code %s",
entry.name,
entry.process.pid,
exitcode,
)
now = datetime.datetime.now().timestamp()
if entry.is_restarting_too_fast(now):
logger.error(
"Process %s restarting too frequently (%d times in %ds), backing off",
entry.name,
MAX_RESTARTS,
RESTART_WINDOW_S,
)
return
try:
entry.process.close()
new_process = entry.factory()
new_process.start()
entry.process = new_process
entry.restart_timestamps.append(now)
if entry.on_restart:
entry.on_restart(new_process)
logger.info("Restarted %s (PID %s)", entry.name, new_process.pid)
except Exception:
logger.exception("Failed to restart %s", entry.name)
2020-11-01 15:45:04 -06:00
def _check_detectors(self) -> None:
now = datetime.datetime.now().timestamp()
for name, detector in self.detectors.items():
detection_start = detector.detection_start.value # type: ignore[attr-defined]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
if detection_start > 0.0 and now - detection_start > 10:
logger.info(
"Detection appears to be stuck. Restarting detection process..."
)
detector.start_or_restart()
raise_notice("detector_stuck", scope=name, params={"detector": name})
elif (
detector.detect_process is not None
and not detector.detect_process.is_alive()
):
logger.info("Detection appears to have stopped. Exiting Frigate...")
restart_frigate()
2022-04-12 22:24:45 +02:00
def run(self) -> None:
2020-11-01 15:45:04 -06:00
time.sleep(10)
2021-05-21 11:39:14 -04:00
while not self.stop_event.wait(10):
self._check_detectors()
2021-05-21 11:39:14 -04:00
2026-03-20 19:02:47 -05:00
for entry in self._monitored:
self._check_process(entry)
2023-05-29 12:31:17 +02:00
logger.info("Exiting watchdog...")