From cfe2df9ebd3a7b84ae2bdc6b4ae853b3736a7d5f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Apr 2021 14:00:22 -1000 Subject: [PATCH] Prevent config entry retry from blocking startup (#48660) - If there are two integrations doing long retries async_block_till_done() will never be done --- homeassistant/config_entries.py | 16 +++++++++++----- tests/test_config_entries.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 65cae7942a6..23758cf88f2 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -11,7 +11,8 @@ import weakref import attr from homeassistant import data_entry_flow, loader -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.const import EVENT_HOMEASSISTANT_STARTED +from homeassistant.core import CALLBACK_TYPE, CoreState, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers.event import Event @@ -276,14 +277,19 @@ class ConfigEntry: wait_time, ) - async def setup_again(now: Any) -> None: + async def setup_again(*_: Any) -> None: """Run setup again.""" self._async_cancel_retry_setup = None await self.async_setup(hass, integration=integration, tries=tries) - self._async_cancel_retry_setup = hass.helpers.event.async_call_later( - wait_time, setup_again - ) + if hass.state == CoreState.running: + self._async_cancel_retry_setup = hass.helpers.event.async_call_later( + wait_time, setup_again + ) + else: + self._async_cancel_retry_setup = hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, setup_again + ) return except Exception: # pylint: disable=broad-except _LOGGER.exception( diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 4db1952dbfb..c35ba61a767 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -6,7 +6,8 @@ from unittest.mock import AsyncMock, patch import pytest from homeassistant import config_entries, data_entry_flow, loader -from homeassistant.core import callback +from homeassistant.const import EVENT_HOMEASSISTANT_STARTED +from homeassistant.core import CoreState, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -904,6 +905,33 @@ async def test_setup_retrying_during_unload(hass): assert len(mock_call.return_value.mock_calls) == 1 +async def test_setup_retrying_during_unload_before_started(hass): + """Test if we unload an entry that is in retry mode before started.""" + entry = MockConfigEntry(domain="test") + hass.state = CoreState.starting + initial_listeners = hass.bus.async_listeners()[EVENT_HOMEASSISTANT_STARTED] + + mock_setup_entry = AsyncMock(side_effect=ConfigEntryNotReady) + mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry)) + mock_entity_platform(hass, "config_flow.test", None) + + await entry.async_setup(hass) + await hass.async_block_till_done() + + assert entry.state == config_entries.ENTRY_STATE_SETUP_RETRY + assert ( + hass.bus.async_listeners()[EVENT_HOMEASSISTANT_STARTED] == initial_listeners + 1 + ) + + await entry.async_unload(hass) + await hass.async_block_till_done() + + assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED + assert ( + hass.bus.async_listeners()[EVENT_HOMEASSISTANT_STARTED] == initial_listeners + 0 + ) + + async def test_entry_options(hass, manager): """Test that we can set options on an entry.""" entry = MockConfigEntry(domain="test", data={"first": True}, options=None)