From 03362bec1ccef423febe62a23694619d3460e550 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Oct 2022 13:56:20 -0500 Subject: [PATCH] Defer bluetooth scanner watchdog restart if one is already in progress (#80679) --- homeassistant/components/bluetooth/scanner.py | 6 ++ tests/components/bluetooth/test_scanner.py | 66 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/homeassistant/components/bluetooth/scanner.py b/homeassistant/components/bluetooth/scanner.py index 8d4adfe9a3d..fe795f7ace5 100644 --- a/homeassistant/components/bluetooth/scanner.py +++ b/homeassistant/components/bluetooth/scanner.py @@ -347,6 +347,12 @@ class HaScanner(BaseHaScanner): ) if time_since_last_detection < SCANNER_WATCHDOG_TIMEOUT: return + if self._start_stop_lock.locked(): + _LOGGER.debug( + "%s: Scanner is already restarting, deferring restart", + self.name, + ) + return _LOGGER.info( "%s: Bluetooth scanner has gone quiet for %ss, restarting", self.name, diff --git a/tests/components/bluetooth/test_scanner.py b/tests/components/bluetooth/test_scanner.py index 512815b1239..c3a08ac3361 100644 --- a/tests/components/bluetooth/test_scanner.py +++ b/tests/components/bluetooth/test_scanner.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration scanners.""" +import asyncio from datetime import timedelta import time from unittest.mock import MagicMock, patch @@ -487,3 +488,68 @@ async def test_adapter_fails_to_start_and_takes_a_bit_to_init( assert len(mock_recover_adapter.mock_calls) == 1 assert "Waiting for adapter to initialize" in caplog.text + + +async def test_restart_takes_longer_than_watchdog_time(hass, one_adapter, caplog): + """Test we do not try to recover the adapter again if the restart is still in progress.""" + + release_start_event = asyncio.Event() + called_start = 0 + + class MockBleakScanner: + async def start(self, *args, **kwargs): + """Mock Start.""" + nonlocal called_start + called_start += 1 + if called_start == 1: + return + await release_start_event.wait() + + async def stop(self, *args, **kwargs): + """Mock Start.""" + + @property + def discovered_devices(self): + """Mock discovered_devices.""" + return [] + + def register_detection_callback(self, callback: AdvertisementDataCallback): + """Mock Register Detection Callback.""" + + scanner = MockBleakScanner() + start_time_monotonic = time.monotonic() + + with patch( + "homeassistant.components.bluetooth.scanner.ADAPTER_INIT_TIME", + 0, + ), patch( + "homeassistant.components.bluetooth.scanner.MONOTONIC_TIME", + return_value=start_time_monotonic, + ), patch( + "homeassistant.components.bluetooth.scanner.OriginalBleakScanner", + return_value=scanner, + ), patch( + "homeassistant.components.bluetooth.util.recover_adapter", return_value=True + ): + await async_setup_with_one_adapter(hass) + + assert called_start == 1 + + # Now force a recover adapter 2x + for _ in range(2): + with patch( + "homeassistant.components.bluetooth.scanner.MONOTONIC_TIME", + return_value=start_time_monotonic + + SCANNER_WATCHDOG_TIMEOUT + + SCANNER_WATCHDOG_INTERVAL.total_seconds(), + ): + async_fire_time_changed( + hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL + ) + await asyncio.sleep(0) + + # Now release the start event + release_start_event.set() + await hass.async_block_till_done() + + assert "already restarting" in caplog.text