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
|
# has started so we do not block shutdown
|
||||||
if not hass.is_stopping:
|
if not hass.is_stopping:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
self.async_setup(hass),
|
self._async_setup_retry(hass),
|
||||||
f"config entry retry {self.domain} {self.title}",
|
f"config entry retry {self.domain} {self.title}",
|
||||||
eager_start=True,
|
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
|
@callback
|
||||||
def async_shutdown(self) -> None:
|
def async_shutdown(self) -> None:
|
||||||
"""Call when Home Assistant is stopping."""
|
"""Call when Home Assistant is stopping."""
|
||||||
@ -1762,11 +1771,22 @@ class ConfigEntries:
|
|||||||
async def async_reload(self, entry_id: str) -> bool:
|
async def async_reload(self, entry_id: str) -> bool:
|
||||||
"""Reload an entry.
|
"""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 an entry was not loaded, will just load.
|
||||||
"""
|
"""
|
||||||
if (entry := self.async_get_entry(entry_id)) is None:
|
if (entry := self.async_get_entry(entry_id)) is None:
|
||||||
raise UnknownEntry
|
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:
|
async with entry.reload_lock:
|
||||||
unload_result = await self.async_unload(entry_id)
|
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
|
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(
|
async def test_create_entry_options(
|
||||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||||
) -> None:
|
) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user