diff --git a/frigate/test/test_camera_watchdog.py b/frigate/test/test_camera_watchdog.py index 56c318abbb..0167c1aa45 100644 --- a/frigate/test/test_camera_watchdog.py +++ b/frigate/test/test_camera_watchdog.py @@ -165,6 +165,49 @@ class TestCameraWatchdogStreamHealth(unittest.TestCase): assert watchdog._stream_staleness(STREAM_TYPE_MAIN, now) is None assert watchdog._stream_staleness(STREAM_TYPE_SUB, now) is not None + def test_detect_reset_grants_the_shared_sub_stream_grace(self): + watchdog = self._build_watchdog() + watchdog.detect_process_records_sub = True + watchdog.ffmpeg_detect_process = MagicMock() + watchdog.capture_thread = MagicMock() + watchdog.capture_thread.is_alive.return_value = False + watchdog.start_ffmpeg_detect = MagicMock() + + now = datetime.now().astimezone(UTC) + stale = (now - timedelta(hours=1)).timestamp() + watchdog.latest_cache_segment_time[STREAM_TYPE_SUB] = stale + watchdog.latest_valid_segment_time[STREAM_TYPE_SUB] = stale + assert watchdog._stream_staleness(STREAM_TYPE_SUB, now) is not None + + watchdog.reset_capture_thread(terminate=False) + + # the sub check runs later in the same tick against a stale can_restart, + # so without this grace it would kill the just-restarted process again + assert ( + watchdog._stream_staleness(STREAM_TYPE_SUB, datetime.now().astimezone(UTC)) + is None + ) + + def test_detect_reset_leaves_sub_alone_when_not_shared(self): + watchdog = self._build_watchdog() + watchdog.detect_process_records_sub = False + watchdog.ffmpeg_detect_process = MagicMock() + watchdog.capture_thread = MagicMock() + watchdog.capture_thread.is_alive.return_value = False + watchdog.start_ffmpeg_detect = MagicMock() + + now = datetime.now().astimezone(UTC) + stale = (now - timedelta(hours=1)).timestamp() + watchdog.latest_cache_segment_time[STREAM_TYPE_SUB] = stale + watchdog.latest_valid_segment_time[STREAM_TYPE_SUB] = stale + + watchdog.reset_capture_thread(terminate=False) + + assert ( + watchdog._stream_staleness(STREAM_TYPE_SUB, datetime.now().astimezone(UTC)) + is not None + ) + def test_stale_threshold_follows_each_stream_segment_time(self): watchdog = self._build_watchdog( output_args={ diff --git a/frigate/video/ffmpeg.py b/frigate/video/ffmpeg.py index 39ed2a6f31..c55c127498 100644 --- a/frigate/video/ffmpeg.py +++ b/frigate/video/ffmpeg.py @@ -326,6 +326,11 @@ class CameraWatchdog(threading.Thread): self.logger.info("Restarting ffmpeg...") self.start_ffmpeg_detect() + # this process produces the sub stream's segments too, so it gets the + # same startup grace however the reset was triggered + if self.detect_process_records_sub: + self._grant_restart_grace([STREAM_TYPE_SUB], datetime.now().astimezone(UTC)) + def run(self) -> None: if self._update_enabled_state(): self.start_all_ffmpeg() @@ -560,7 +565,6 @@ class CameraWatchdog(threading.Thread): ) self._send_record_status(STREAM_TYPE_SUB, "offline", now) self.reset_capture_thread() - self._grant_restart_grace([STREAM_TYPE_SUB], now_utc) last_restart_time = now # Prune expired reconnect timestamps