Skip to content

Commit

Permalink
more fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco committed May 20, 2024
1 parent a5b5e6e commit e0f94e4
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 31 deletions.
25 changes: 8 additions & 17 deletions homeassistant/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,36 +718,27 @@ def _async_setup_again(self, hass: HomeAssistant, *_: Any) -> None:
# has started so we do not block shutdown
if not hass.is_stopping:
hass.async_create_background_task(
self._async_setup_again_locked(hass),
self.async_setup_locked(hass),
f"config entry retry {self.domain} {self.title}",
eager_start=True,
)

async def _async_setup_again_locked(self, hass: HomeAssistant) -> None:
"""Schedule setup again while holding the setup lock."""
async def async_setup_locked(
self, hass: HomeAssistant, integration: loader.Integration | None = None
) -> None:
"""Set up while holding the setup lock."""
async with self.setup_lock:
if (state := self.state) in (
ConfigEntryState.LOADED,
ConfigEntryState.SETUP_IN_PROGRESS,
):
# If something reloaded the config entry while
if self.state is ConfigEntryState.LOADED:
# If something loaded the config entry while
# we were waiting for the lock, we should not
# set it up again.
_LOGGER.debug(
"Not setting up %s (%s %s) again, already %s",
"Not setting up %s (%s %s) again, already loaded",
self.title,
self.domain,
self.entry_id,
state,
)
return
await self.async_setup(hass)

async def async_setup_locked(
self, hass: HomeAssistant, integration: loader.Integration | None = None
) -> None:
"""Set up while holding the setup lock."""
async with self.setup_lock:
await self.async_setup(hass, integration=integration)

@callback
Expand Down
63 changes: 49 additions & 14 deletions tests/test_config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,29 @@ async def manager(hass: HomeAssistant) -> config_entries.ConfigEntries:
return manager


async def test_setup_retry_race(hass: HomeAssistant) -> None:
"""Test handling setup retry when a config entry has been reloaded."""
async def test_setup_race_only_setup_once(hass: HomeAssistant) -> None:
"""Test ensure that config entries are only setup once."""
attempts = 0
slow_config_entry_setup_future = hass.loop.create_future()
fast_config_entry_setup_future = hass.loop.create_future()
slow_setup_future = hass.loop.create_future()

async def async_setup(hass, config):
"""Mock setup."""
await slow_setup_future
return True

async def async_setup_entry(hass, entry):
"""Mock setup entry."""
slow = entry.data["slow"]
if slow:
await slow_config_entry_setup_future
return True
nonlocal attempts
attempts += 1
if attempts == 1:
raise ConfigEntryNotReady
await asyncio.sleep(0.1)
await fast_config_entry_setup_future
return True

async def async_unload_entry(hass, entry):
Expand All @@ -111,31 +123,54 @@ async def async_unload_entry(hass, entry):
hass,
MockModule(
"comp",
async_setup=async_setup,
async_setup_entry=async_setup_entry,
async_unload_entry=async_unload_entry,
),
)
mock_platform(hass, "comp.config_flow", None)

entry = MockConfigEntry(domain="comp")
entry = MockConfigEntry(domain="comp", data={"slow": False})
entry.add_to_hass(hass)

await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
entry2 = MockConfigEntry(domain="comp", data={"slow": True})
entry2.add_to_hass(hass)
await entry2.setup_lock.acquire()

# Ensure setup retry has started, but do not block until its
# done so we can trigger a reload before its complete
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
assert entry.setup_lock.locked()
assert entry._async_cancel_retry_setup is None
async def _async_reload_entry(entry: MockConfigEntry):
async with entry.setup_lock:
await entry.async_unload(hass)
await entry.async_setup(hass)

hass.config_entries.async_schedule_reload(entry.entry_id)
assert entry.state is config_entries.ConfigEntryState.SETUP_IN_PROGRESS
hass.async_create_task(_async_reload_entry(entry2))

async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
setup_task = hass.async_create_task(async_setup_component(hass, "comp", {}))
entry2.setup_lock.release()

assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
assert entry2.state is config_entries.ConfigEntryState.NOT_LOADED

assert "comp" not in hass.config.components
slow_setup_future.set_result(None)
await asyncio.sleep(0)
assert "comp" in hass.config.components

assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
assert entry2.state is config_entries.ConfigEntryState.SETUP_IN_PROGRESS

fast_config_entry_setup_future.set_result(None)
# Make sure setup retry is started
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5))
slow_config_entry_setup_future.set_result(None)
await hass.async_block_till_done()

assert entry.state is config_entries.ConfigEntryState.LOADED
await hass.async_block_till_done()

assert attempts == 2
await hass.async_block_till_done()
assert setup_task.done()
assert entry2.state is config_entries.ConfigEntryState.LOADED


async def test_call_setup_entry(hass: HomeAssistant) -> None:
Expand Down

0 comments on commit e0f94e4

Please sign in to comment.