mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Hold the reload lock while attempting config entry setup retry (#115538)
This commit is contained in:
parent
b9d4d0e15d
commit
82d0f478a5
@ -699,11 +699,20 @@ class ConfigEntry:
|
||||
# has started so we do not block shutdown
|
||||
if not hass.is_stopping:
|
||||
hass.async_create_task(
|
||||
self.async_setup(hass),
|
||||
self._async_setup_retry(hass),
|
||||
f"config entry retry {self.domain} {self.title}",
|
||||
eager_start=True,
|
||||
)
|
||||
|
||||
async def _async_setup_retry(self, hass: HomeAssistant) -> None:
|
||||
"""Retry setup.
|
||||
|
||||
We hold the reload lock during setup retry to ensure
|
||||
that nothing can reload the entry while we are retrying.
|
||||
"""
|
||||
async with self.reload_lock:
|
||||
await self.async_setup(hass)
|
||||
|
||||
@callback
|
||||
def async_shutdown(self) -> None:
|
||||
"""Call when Home Assistant is stopping."""
|
||||
@ -1762,11 +1771,22 @@ class ConfigEntries:
|
||||
async def async_reload(self, entry_id: str) -> bool:
|
||||
"""Reload an entry.
|
||||
|
||||
When reloading from an integration is is preferable to
|
||||
call async_schedule_reload instead of this method since
|
||||
it will cancel setup retry before starting this method
|
||||
in a task which eliminates a race condition where the
|
||||
setup retry can fire during the reload.
|
||||
|
||||
If an entry was not loaded, will just load.
|
||||
"""
|
||||
if (entry := self.async_get_entry(entry_id)) is None:
|
||||
raise UnknownEntry
|
||||
|
||||
# Cancel the setup retry task before waiting for the
|
||||
# reload lock to reduce the chance of concurrent reload
|
||||
# attempts.
|
||||
entry.async_cancel_retry_setup()
|
||||
|
||||
async with entry.reload_lock:
|
||||
unload_result = await self.async_unload(entry_id)
|
||||
|
||||
|
@ -1284,6 +1284,53 @@ async def test_setup_does_not_retry_during_shutdown(hass: HomeAssistant) -> None
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_reload_during_setup_retrying_waits(hass: HomeAssistant) -> None:
|
||||
"""Test reloading during setup retry waits."""
|
||||
entry = MockConfigEntry(domain="test")
|
||||
entry.add_to_hass(hass)
|
||||
load_attempts = []
|
||||
sleep_duration = 0
|
||||
|
||||
async def _mock_setup_entry(hass, entry):
|
||||
"""Mock setup entry."""
|
||||
nonlocal sleep_duration
|
||||
await asyncio.sleep(sleep_duration)
|
||||
load_attempts.append(entry.entry_id)
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
mock_integration(hass, MockModule("test", async_setup_entry=_mock_setup_entry))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
|
||||
await hass.async_create_task(
|
||||
hass.config_entries.async_setup(entry.entry_id), eager_start=True
|
||||
)
|
||||
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
|
||||
|
||||
# Now make the setup take a while so that the setup retry
|
||||
# will still be in progress when the reload request comes in
|
||||
sleep_duration = 0.1
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
|
||||
await asyncio.sleep(0)
|
||||
|
||||
# Should not raise homeassistant.config_entries.OperationNotAllowed
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
|
||||
await asyncio.sleep(0)
|
||||
|
||||
# Should not raise homeassistant.config_entries.OperationNotAllowed
|
||||
hass.config_entries.async_schedule_reload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert load_attempts == [
|
||||
entry.entry_id,
|
||||
entry.entry_id,
|
||||
entry.entry_id,
|
||||
entry.entry_id,
|
||||
entry.entry_id,
|
||||
]
|
||||
|
||||
|
||||
async def test_create_entry_options(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user