From d5e1934942f4c629e896603787b6970df3c88004 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 26 Feb 2024 18:31:08 -1000 Subject: [PATCH] Use an eager task in the update coordinator scheduled refresh (#111570) * Use an eager task in the update coordinator scheduled refresh We have a lot of places that will not suspend because the refresh function decides it does not need to update. Currently these have to be scheduled on the event loop even though they are a noop. Since _handle_refresh_interval is subclassed in some integrations, I created a dunder wrapper function to avoid integraions subclassing it * fix time fires outside of patch --- homeassistant/helpers/update_coordinator.py | 9 +++++++-- tests/components/bmw_connected_drive/test_coordinator.py | 7 ++++--- tests/components/roborock/test_image.py | 5 +++-- tests/components/switcher_kis/test_init.py | 1 + 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index f76ff3b89b7..018ba1d13e4 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -112,9 +112,9 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]): if entry := self.config_entry: job_name += f" {entry.title} {entry.domain} {entry.entry_id}" self._job = HassJob( - self._handle_refresh_interval, + self.__wrap_handle_refresh_interval, job_name, - job_type=HassJobType.Coroutinefunction, + job_type=HassJobType.Callback, ) self._unsub_refresh: CALLBACK_TYPE | None = None self._unsub_shutdown: CALLBACK_TYPE | None = None @@ -250,6 +250,11 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]): next_refresh, hass.async_run_hass_job, self._job ).cancel + @callback + def __wrap_handle_refresh_interval(self) -> None: + """Handle a refresh interval occurrence.""" + self.hass.async_create_task(self._handle_refresh_interval(), eager_start=True) + async def _handle_refresh_interval(self, _now: datetime | None = None) -> None: """Handle a refresh interval occurrence.""" self._unsub_refresh = None diff --git a/tests/components/bmw_connected_drive/test_coordinator.py b/tests/components/bmw_connected_drive/test_coordinator.py index ab2d08376dd..bf47b7fed29 100644 --- a/tests/components/bmw_connected_drive/test_coordinator.py +++ b/tests/components/bmw_connected_drive/test_coordinator.py @@ -44,11 +44,12 @@ async def test_update_failed( assert coordinator.last_update_success is True freezer.tick(timedelta(minutes=5, seconds=1)) - async_fire_time_changed(hass) + with patch( "bimmer_connected.account.MyBMWAccount.get_vehicles", side_effect=MyBMWAPIError("Test error"), ): + async_fire_time_changed(hass) await hass.async_block_till_done() assert coordinator.last_update_success is False @@ -70,22 +71,22 @@ async def test_update_reauth( assert coordinator.last_update_success is True freezer.tick(timedelta(minutes=5, seconds=1)) - async_fire_time_changed(hass) with patch( "bimmer_connected.account.MyBMWAccount.get_vehicles", side_effect=MyBMWAuthError("Test error"), ): + async_fire_time_changed(hass) await hass.async_block_till_done() assert coordinator.last_update_success is False assert isinstance(coordinator.last_exception, UpdateFailed) is True freezer.tick(timedelta(minutes=5, seconds=1)) - async_fire_time_changed(hass) with patch( "bimmer_connected.account.MyBMWAccount.get_vehicles", side_effect=MyBMWAuthError("Test error"), ): + async_fire_time_changed(hass) await hass.async_block_till_done() assert coordinator.last_update_success is False diff --git a/tests/components/roborock/test_image.py b/tests/components/roborock/test_image.py index 80d4bd37337..19fa5152e2d 100644 --- a/tests/components/roborock/test_image.py +++ b/tests/components/roborock/test_image.py @@ -30,7 +30,7 @@ async def test_floorplan_image( assert body is not None # Call a third time - this time forcing it to update now = dt_util.utcnow() + timedelta(seconds=91) - async_fire_time_changed(hass, now) + # Copy the device prop so we don't override it prop = copy.deepcopy(PROP) prop.status.in_cleaning = 1 @@ -40,6 +40,7 @@ async def test_floorplan_image( ), patch( "homeassistant.components.roborock.image.dt_util.utcnow", return_value=now ): + async_fire_time_changed(hass, now) await hass.async_block_till_done() resp = await client.get("/api/image_proxy/image.roborock_s7_maxv_upstairs") assert resp.status == HTTPStatus.OK @@ -57,7 +58,6 @@ async def test_floorplan_image_failed_parse( map_data = copy.deepcopy(MAP_DATA) map_data.image = None now = dt_util.utcnow() + timedelta(seconds=91) - async_fire_time_changed(hass, now) # Copy the device prop so we don't override it prop = copy.deepcopy(PROP) prop.status.in_cleaning = 1 @@ -71,5 +71,6 @@ async def test_floorplan_image_failed_parse( ), patch( "homeassistant.components.roborock.image.dt_util.utcnow", return_value=now ): + async_fire_time_changed(hass, now) resp = await client.get("/api/image_proxy/image.roborock_s7_maxv_upstairs") assert not resp.ok diff --git a/tests/components/switcher_kis/test_init.py b/tests/components/switcher_kis/test_init.py index 55be7d93905..d35fe6e9d20 100644 --- a/tests/components/switcher_kis/test_init.py +++ b/tests/components/switcher_kis/test_init.py @@ -88,6 +88,7 @@ async def test_update_fail( async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=MAX_UPDATE_INTERVAL_SEC - 1) ) + await hass.async_block_till_done() for device in DUMMY_SWITCHER_DEVICES: entity_id = f"switch.{slugify(device.name)}"