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
This commit is contained in:
J. Nick Koston 2023-06-15 16:15:07 -10:00 committed by GitHub
parent 7e3510800d
commit 34b725bb99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 41 additions and 15 deletions

View File

@ -29,6 +29,7 @@ from .exceptions import (
HomeAssistantError, HomeAssistantError,
) )
from .helpers import device_registry, entity_registry, storage from .helpers import device_registry, entity_registry, storage
from .helpers.debounce import Debouncer
from .helpers.dispatcher import async_dispatcher_send from .helpers.dispatcher import async_dispatcher_send
from .helpers.event import ( from .helpers.event import (
RANDOM_MICROSECOND_MAX, RANDOM_MICROSECOND_MAX,
@ -88,6 +89,8 @@ PATH_CONFIG = ".config_entries.json"
SAVE_DELAY = 1 SAVE_DELAY = 1
DISCOVERY_COOLDOWN = 1
_R = TypeVar("_R") _R = TypeVar("_R")
@ -808,6 +811,13 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager):
self._hass_config = hass_config self._hass_config = hass_config
self._pending_import_flows: dict[str, dict[str, asyncio.Future[None]]] = {} self._pending_import_flows: dict[str, dict[str, asyncio.Future[None]]] = {}
self._initialize_tasks: dict[str, list[asyncio.Task]] = {} 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: async def async_wait_import_flow_initialized(self, handler: str) -> None:
"""Wait till all import flows in progress are initialized.""" """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_list in self._initialize_tasks.values():
for task in task_list: for task in task_list:
task.cancel() task.cancel()
await self._discovery_debouncer.async_shutdown()
async def async_finish_flow( async def async_finish_flow(
self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult
@ -979,16 +990,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager):
# Create notification. # Create notification.
if source in DISCOVERY_SOURCES: if source in DISCOVERY_SOURCES:
self.hass.bus.async_fire(EVENT_FLOW_DISCOVERED) await self._discovery_debouncer.async_call()
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,
)
elif source == SOURCE_REAUTH: elif source == SOURCE_REAUTH:
persistent_notification.async_create( persistent_notification.async_create(
self.hass, self.hass,
@ -1000,6 +1002,20 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager):
notification_id=RECONFIGURE_NOTIFICATION_ID, 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: class ConfigEntries:
"""Manage the configuration entries. """Manage the configuration entries.

View File

@ -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 # Load the registries
entity.async_setup(hass) entity.async_setup(hass)

View File

@ -68,9 +68,10 @@ def mock_handlers() -> Generator[None, None, None]:
@pytest.fixture @pytest.fixture
def manager(hass: HomeAssistant) -> config_entries.ConfigEntries: async def manager(hass: HomeAssistant) -> config_entries.ConfigEntries:
"""Fixture of a loaded config manager.""" """Fixture of a loaded config manager."""
manager = config_entries.ConfigEntries(hass, {}) manager = config_entries.ConfigEntries(hass, {})
await manager.async_initialize()
hass.config_entries = manager hass.config_entries = manager
return 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 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.""" """Test that we create/dismiss a notification when source is discovery."""
mock_integration(hass, MockModule("test")) mock_integration(hass, MockModule("test"))
mock_entity_platform(hass, "config_flow.test", None) 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 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.""" """Test a config entry being created with options."""
async def mock_async_setup(hass, config): async def mock_async_setup(hass, config):
@ -2482,7 +2487,9 @@ async def test_partial_flows_hidden(
assert "config_entry_discovery" in notifications 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.""" """Test a config entry being initialized during integration setup."""
async def mock_async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 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( async def test_async_setup_init_entry_completes_before_loaded_event_fires(
hass: HomeAssistant, hass: HomeAssistant, manager: config_entries.ConfigEntries
) -> None: ) -> None:
"""Test a config entry being initialized during integration setup before the loaded event fires.""" """Test a config entry being initialized during integration setup before the loaded event fires."""
load_events: list[Event] = [] load_events: list[Event] = []