From 79ad9a3646135f4ff0dfb5cbaa67700e15e7bbdb Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 21 Apr 2023 10:07:31 +0200 Subject: [PATCH] Shutdown coordinator on entry unload (#91748) Co-authored-by: J. Nick Koston --- homeassistant/helpers/debounce.py | 5 +++- homeassistant/helpers/update_coordinator.py | 3 ++ tests/helpers/test_debounce.py | 10 +++++-- tests/helpers/test_update_coordinator.py | 33 +++++++++++++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/debounce.py b/homeassistant/helpers/debounce.py index 97381975f93..2df8965de34 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -64,7 +64,10 @@ class Debouncer(Generic[_R_co]): async def async_call(self) -> None: """Call the function.""" if self._shutdown_requested: - raise RuntimeError("Debouncer has been shutdown.") + self.logger.warning( + "Debouncer call ignored as shutdown has been requested." + ) + return assert self._job is not None if self._timer_task: diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 79de705eb49..c563ef09a52 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -114,6 +114,9 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_T]): self._debounced_refresh = request_refresh_debouncer + if self.config_entry: + self.config_entry.async_on_unload(self.async_shutdown) + @callback def async_add_listener( self, update_callback: CALLBACK_TYPE, context: Any = None diff --git a/tests/helpers/test_debounce.py b/tests/helpers/test_debounce.py index ae69d40bdcb..4d36679d538 100644 --- a/tests/helpers/test_debounce.py +++ b/tests/helpers/test_debounce.py @@ -182,7 +182,7 @@ async def test_immediate_works_with_function_swapped(hass: HomeAssistant) -> Non assert debouncer._job.target == debouncer.function -async def test_shutdown(hass: HomeAssistant) -> None: +async def test_shutdown(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None: """Test shutdown.""" calls = [] future = asyncio.Future() @@ -208,5 +208,9 @@ async def test_shutdown(hass: HomeAssistant) -> None: assert len(calls) == 1 assert debouncer._timer_task is None - with pytest.raises(RuntimeError, match="Debouncer has been shutdown"): - await debouncer.async_call() + assert "Debouncer call ignored as shutdown has been requested." not in caplog.text + await debouncer.async_call() + assert "Debouncer call ignored as shutdown has been requested." in caplog.text + + assert len(calls) == 1 + assert debouncer._timer_task is None diff --git a/tests/helpers/test_update_coordinator.py b/tests/helpers/test_update_coordinator.py index 3ff0e1e1b21..35ae78834c5 100644 --- a/tests/helpers/test_update_coordinator.py +++ b/tests/helpers/test_update_coordinator.py @@ -153,6 +153,39 @@ async def test_shutdown( assert updates == [2] +async def test_shutdown_on_entry_unload( + hass: HomeAssistant, + crd: update_coordinator.DataUpdateCoordinator[int], +) -> None: + """Test shutdown is requested on entry unload.""" + entry = MockConfigEntry() + config_entries.current_entry.set(entry) + + calls = 0 + + async def _refresh() -> int: + nonlocal calls + calls += 1 + return calls + + crd = update_coordinator.DataUpdateCoordinator[int]( + hass, + _LOGGER, + name="test", + update_method=_refresh, + update_interval=DEFAULT_UPDATE_INTERVAL, + ) + + crd.async_add_listener(lambda: None) + assert crd._unsub_refresh is not None + assert not crd._shutdown_requested + + await entry._async_process_on_unload(hass) + + assert crd._shutdown_requested + assert crd._unsub_refresh is None + + async def test_update_context( crd: update_coordinator.DataUpdateCoordinator[int], ) -> None: