Schedule polling as periodic tasks (#112640)

* Schedule periodic coordinator updates as background tasks.

Currently, the coordinator's periodic refreshes delay startup because they are not scheduled as background tasks. We will wait if the startup takes long enough for the first planned refresh. Another coordinator's scheduled refresh will be fired on busy systems, further delaying the startup. This chain of events results in the startup taking a long time and hitting the safety timeout because too many coordinators are refreshing.

This case can also happen with scheduled entity refreshes, but it's less common. A future PR will address that case.

* periodic_tasks

* periodic_tasks

* periodic_tasks

* merge

* merge

* merge

* merge

* merge

* fix test that call the sync api from async

* one more place

* cannot chain

* async_run_periodic_hass_job

* sun and pattern time changes from automations also block startup

* Revert "sun and pattern time changes from automations also block startup"

This reverts commit 6de2defa05.

* make sure polling is cancelled when config entry is unloaded

* Revert "Revert "sun and pattern time changes from automations also block startup""

This reverts commit e8f12aad55.

* remove DisabledError from homewizard test as it relies on a race

* fix race

* direct coverage
This commit is contained in:
J. Nick Koston 2024-03-07 18:32:26 -10:00 committed by GitHub
parent 5da629b3e5
commit a6b17dbe68
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 292 additions and 51 deletions

View file

@ -3,14 +3,17 @@ from datetime import timedelta
import os
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant import config_entries
from homeassistant.components.hassio import (
DOMAIN,
HASSIO_UPDATE_INTERVAL,
HassioAPIError,
)
from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
@ -264,6 +267,7 @@ async def test_sensor(
("sensor.test_memory_percent", "4.59"),
],
)
@patch.dict(os.environ, MOCK_ENVIRON)
async def test_stats_addon_sensor(
hass: HomeAssistant,
entity_id,
@ -271,18 +275,17 @@ async def test_stats_addon_sensor(
aioclient_mock: AiohttpClientMocker,
entity_registry: er.EntityRegistry,
caplog: pytest.LogCaptureFixture,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test stats addons sensor."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
config_entry.add_to_hass(hass)
with patch.dict(os.environ, MOCK_ENVIRON):
result = await async_setup_component(
hass,
"hassio",
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
)
assert result
assert await async_setup_component(
hass,
"hassio",
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
)
await hass.async_block_till_done()
# Verify that the entity is disabled by default.
@ -292,9 +295,8 @@ async def test_stats_addon_sensor(
_install_default_mocks(aioclient_mock)
_install_test_addon_stats_failure_mock(aioclient_mock)
async_fire_time_changed(
hass, dt_util.utcnow() + HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)
)
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert "Could not fetch stats" not in caplog.text
@ -303,22 +305,31 @@ async def test_stats_addon_sensor(
_install_default_mocks(aioclient_mock)
_install_test_addon_stats_mock(aioclient_mock)
async_fire_time_changed(
hass, dt_util.utcnow() + HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)
)
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
# Enable the entity.
assert "Could not fetch stats" not in caplog.text
# Enable the entity and wait for the reload to complete.
entity_registry.async_update_entity(entity_id, disabled_by=None)
await hass.config_entries.async_reload(config_entry.entry_id)
freezer.tick(config_entries.RELOAD_AFTER_UPDATE_DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert config_entry.state is config_entries.ConfigEntryState.LOADED
# Verify the entity is still enabled
assert entity_registry.async_get(entity_id).disabled_by is None
# The config entry just reloaded, so we need to wait for the next update
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
assert hass.states.get(entity_id) is not None
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
# Verify that the entity have the expected state.
state = hass.states.get(entity_id)
assert state.state == expected
@ -327,9 +338,10 @@ async def test_stats_addon_sensor(
_install_default_mocks(aioclient_mock)
_install_test_addon_stats_failure_mock(aioclient_mock)
async_fire_time_changed(
hass, dt_util.utcnow() + HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)
)
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == STATE_UNAVAILABLE
assert "Could not fetch stats" in caplog.text