Refactor entity_platform polling to avoid double time fetch (#116877)

* Refactor entity_platform polling to avoid double time fetch

Replace async_track_time_interval with loop.call_later
to avoid the useless time fetch every time the listener
fired since we always throw it away

* fix test
This commit is contained in:
J. Nick Koston 2024-05-05 15:28:01 -05:00 committed by GitHub
parent 76cd498c44
commit b41b1bb998
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 36 additions and 38 deletions

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable, Coroutine, Iterable
from contextvars import ContextVar
from datetime import datetime, timedelta
from datetime import timedelta
from functools import partial
from logging import Logger, getLogger
from typing import TYPE_CHECKING, Any, Protocol
@ -43,7 +43,7 @@ from . import (
translation,
)
from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider
from .event import async_call_later, async_track_time_interval
from .event import async_call_later
from .issue_registry import IssueSeverity, async_create_issue
from .typing import UNDEFINED, ConfigType, DiscoveryInfoType
@ -125,6 +125,7 @@ class EntityPlatform:
self.platform_name = platform_name
self.platform = platform
self.scan_interval = scan_interval
self.scan_interval_seconds = scan_interval.total_seconds()
self.entity_namespace = entity_namespace
self.config_entry: config_entries.ConfigEntry | None = None
# Storage for entities for this specific platform only
@ -138,7 +139,7 @@ class EntityPlatform:
# Stop tracking tasks after setup is completed
self._setup_complete = False
# Method to cancel the state change listener
self._async_unsub_polling: CALLBACK_TYPE | None = None
self._async_polling_timer: asyncio.TimerHandle | None = None
# Method to cancel the retry of setup
self._async_cancel_retry_setup: CALLBACK_TYPE | None = None
self._process_updates: asyncio.Lock | None = None
@ -630,7 +631,7 @@ class EntityPlatform:
if (
(self.config_entry and self.config_entry.pref_disable_polling)
or self._async_unsub_polling is not None
or self._async_polling_timer is not None
or not any(
# Entity may have failed to add or called `add_to_platform_abort`
# so we check if the entity is in self.entities before
@ -644,26 +645,28 @@ class EntityPlatform:
):
return
self._async_unsub_polling = async_track_time_interval(
self.hass,
self._async_polling_timer = self.hass.loop.call_later(
self.scan_interval_seconds,
self._async_handle_interval_callback,
self.scan_interval,
name=f"EntityPlatform poll {self.domain}.{self.platform_name}",
)
@callback
def _async_handle_interval_callback(self, now: datetime) -> None:
def _async_handle_interval_callback(self) -> None:
"""Update all the entity states in a single platform."""
self._async_polling_timer = self.hass.loop.call_later(
self.scan_interval_seconds,
self._async_handle_interval_callback,
)
if self.config_entry:
self.config_entry.async_create_background_task(
self.hass,
self._update_entity_states(now),
self._async_update_entity_states(),
name=f"EntityPlatform poll {self.domain}.{self.platform_name}",
eager_start=True,
)
else:
self.hass.async_create_background_task(
self._update_entity_states(now),
self._async_update_entity_states(),
name=f"EntityPlatform poll {self.domain}.{self.platform_name}",
eager_start=True,
)
@ -919,9 +922,9 @@ class EntityPlatform:
@callback
def async_unsub_polling(self) -> None:
"""Stop polling."""
if self._async_unsub_polling is not None:
self._async_unsub_polling()
self._async_unsub_polling = None
if self._async_polling_timer is not None:
self._async_polling_timer.cancel()
self._async_polling_timer = None
@callback
def async_prepare(self) -> None:
@ -943,11 +946,10 @@ class EntityPlatform:
await self.entities[entity_id].async_remove()
# Clean up polling job if no longer needed
if self._async_unsub_polling is not None and not any(
if self._async_polling_timer is not None and not any(
entity.should_poll for entity in self.entities.values()
):
self._async_unsub_polling()
self._async_unsub_polling = None
self.async_unsub_polling()
async def async_extract_from_service(
self, service_call: ServiceCall, expand_group: bool = True
@ -998,7 +1000,7 @@ class EntityPlatform:
supports_response,
)
async def _update_entity_states(self, now: datetime) -> None:
async def _async_update_entity_states(self) -> None:
"""Update the states of all the polling entities.
To protect from flooding the executor, we will update async entities