From 34b725bb99af98b3328f58d343e9b55378911179 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 15 Jun 2023 16:15:07 -1000 Subject: [PATCH] Debounce discoveries to improve event loop stability at the started event (#94690) * Debounce discoveries to improve event loop stability at the started event The first one is immediate and anything that fires within the next second will be debounced to only happen once every second * fix mock --- homeassistant/config_entries.py | 36 ++++++++++++++++++++++++--------- tests/common.py | 3 +++ tests/test_config_entries.py | 17 +++++++++++----- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 14d69e278fa..a52b869b830 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -29,6 +29,7 @@ from .exceptions import ( HomeAssistantError, ) from .helpers import device_registry, entity_registry, storage +from .helpers.debounce import Debouncer from .helpers.dispatcher import async_dispatcher_send from .helpers.event import ( RANDOM_MICROSECOND_MAX, @@ -88,6 +89,8 @@ PATH_CONFIG = ".config_entries.json" SAVE_DELAY = 1 +DISCOVERY_COOLDOWN = 1 + _R = TypeVar("_R") @@ -808,6 +811,13 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): self._hass_config = hass_config self._pending_import_flows: dict[str, dict[str, asyncio.Future[None]]] = {} self._initialize_tasks: dict[str, list[asyncio.Task]] = {} + self._discovery_debouncer = Debouncer( + hass, + _LOGGER, + cooldown=DISCOVERY_COOLDOWN, + immediate=True, + function=self._async_discovery, + ) async def async_wait_import_flow_initialized(self, handler: str) -> None: """Wait till all import flows in progress are initialized.""" @@ -883,6 +893,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): for task_list in self._initialize_tasks.values(): for task in task_list: task.cancel() + await self._discovery_debouncer.async_shutdown() async def async_finish_flow( self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult @@ -979,16 +990,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): # Create notification. if source in DISCOVERY_SOURCES: - self.hass.bus.async_fire(EVENT_FLOW_DISCOVERED) - persistent_notification.async_create( - self.hass, - title="New devices discovered", - message=( - "We have discovered new devices on your network. " - "[Check it out](/config/integrations)." - ), - notification_id=DISCOVERY_NOTIFICATION_ID, - ) + await self._discovery_debouncer.async_call() elif source == SOURCE_REAUTH: persistent_notification.async_create( self.hass, @@ -1000,6 +1002,20 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): notification_id=RECONFIGURE_NOTIFICATION_ID, ) + @callback + def _async_discovery(self) -> None: + """Handle discovery.""" + self.hass.bus.async_fire(EVENT_FLOW_DISCOVERED) + persistent_notification.async_create( + self.hass, + title="New devices discovered", + message=( + "We have discovered new devices on your network. " + "[Check it out](/config/integrations)." + ), + notification_id=DISCOVERY_NOTIFICATION_ID, + ) + class ConfigEntries: """Manage the configuration entries. diff --git a/tests/common.py b/tests/common.py index 38b7cf79b75..9c8ed5bb56e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -248,6 +248,9 @@ async def async_test_home_assistant(event_loop, load_registries=True): ) }, ) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, hass.config_entries._async_shutdown + ) # Load the registries entity.async_setup(hass) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index b20f2af7669..68e6fc59987 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -68,9 +68,10 @@ def mock_handlers() -> Generator[None, None, None]: @pytest.fixture -def manager(hass: HomeAssistant) -> config_entries.ConfigEntries: +async def manager(hass: HomeAssistant) -> config_entries.ConfigEntries: """Fixture of a loaded config manager.""" manager = config_entries.ConfigEntries(hass, {}) + await manager.async_initialize() hass.config_entries = manager return manager @@ -712,7 +713,9 @@ async def test_forward_entry_does_not_setup_entry_if_setup_fails( assert len(mock_setup_entry.mock_calls) == 0 -async def test_discovery_notification(hass: HomeAssistant) -> None: +async def test_discovery_notification( + hass: HomeAssistant, manager: config_entries.ConfigEntries +) -> None: """Test that we create/dismiss a notification when source is discovery.""" mock_integration(hass, MockModule("test")) mock_entity_platform(hass, "config_flow.test", None) @@ -1052,7 +1055,9 @@ async def test_setup_does_not_retry_during_shutdown(hass: HomeAssistant) -> None assert len(mock_setup_entry.mock_calls) == 1 -async def test_create_entry_options(hass: HomeAssistant) -> None: +async def test_create_entry_options( + hass: HomeAssistant, manager: config_entries.ConfigEntries +) -> None: """Test a config entry being created with options.""" async def mock_async_setup(hass, config): @@ -2482,7 +2487,9 @@ async def test_partial_flows_hidden( assert "config_entry_discovery" in notifications -async def test_async_setup_init_entry(hass: HomeAssistant) -> None: +async def test_async_setup_init_entry( + hass: HomeAssistant, manager: config_entries.ConfigEntries +) -> None: """Test a config entry being initialized during integration setup.""" async def mock_async_setup(hass: HomeAssistant, config: ConfigType) -> bool: @@ -2527,7 +2534,7 @@ async def test_async_setup_init_entry(hass: HomeAssistant) -> None: async def test_async_setup_init_entry_completes_before_loaded_event_fires( - hass: HomeAssistant, + hass: HomeAssistant, manager: config_entries.ConfigEntries ) -> None: """Test a config entry being initialized during integration setup before the loaded event fires.""" load_events: list[Event] = []