Avoid looking up the callable type for HassJob when we already know it (#102962)

* Avoid looking up the callable type for HassJob when we already know it

When we connect the frontend we call async_listen with run_immediately MANY
times when we already know the job type (it will always be a callback). This
reduces the latency to get the frontend going

* missing coverage
This commit is contained in:
J. Nick Koston 2023-10-30 06:45:22 -05:00 committed by GitHub
parent 7dbe0c3a48
commit b3743937de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 8 deletions

View file

@ -282,11 +282,12 @@ class HassJob(Generic[_P, _R_co]):
name: str | None = None, name: str | None = None,
*, *,
cancel_on_shutdown: bool | None = None, cancel_on_shutdown: bool | None = None,
job_type: HassJobType | None = None,
) -> None: ) -> None:
"""Create a job object.""" """Create a job object."""
self.target = target self.target = target
self.name = name self.name = name
self.job_type = _get_hassjob_callable_job_type(target) self.job_type = job_type or _get_hassjob_callable_job_type(target)
self._cancel_on_shutdown = cancel_on_shutdown self._cancel_on_shutdown = cancel_on_shutdown
@property @property
@ -1153,13 +1154,20 @@ class EventBus:
This method must be run in the event loop. This method must be run in the event loop.
""" """
job_type: HassJobType | None = None
if event_filter is not None and not is_callback_check_partial(event_filter): if event_filter is not None and not is_callback_check_partial(event_filter):
raise HomeAssistantError(f"Event filter {event_filter} is not a callback") raise HomeAssistantError(f"Event filter {event_filter} is not a callback")
if run_immediately and not is_callback_check_partial(listener): if run_immediately:
raise HomeAssistantError(f"Event listener {listener} is not a callback") if not is_callback_check_partial(listener):
raise HomeAssistantError(f"Event listener {listener} is not a callback")
job_type = HassJobType.Callback
return self._async_listen_filterable_job( return self._async_listen_filterable_job(
event_type, event_type,
(HassJob(listener, f"listen {event_type}"), event_filter, run_immediately), (
HassJob(listener, f"listen {event_type}", job_type=job_type),
event_filter,
run_immediately,
),
) )
@callback @callback
@ -1234,7 +1242,11 @@ class EventBus:
) )
filterable_job = ( filterable_job = (
HassJob(_onetime_listener, f"onetime listen {event_type} {listener}"), HassJob(
_onetime_listener,
f"onetime listen {event_type} {listener}",
job_type=HassJobType.Callback,
),
None, None,
False, False,
) )

View file

@ -24,6 +24,7 @@ from homeassistant.const import (
from homeassistant.core import ( from homeassistant.core import (
CALLBACK_TYPE, CALLBACK_TYPE,
HassJob, HassJob,
HassJobType,
HomeAssistant, HomeAssistant,
State, State,
callback, callback,
@ -1376,6 +1377,7 @@ def async_track_point_in_time(
utc_converter, utc_converter,
name=f"{job.name} UTC converter", name=f"{job.name} UTC converter",
cancel_on_shutdown=job.cancel_on_shutdown, cancel_on_shutdown=job.cancel_on_shutdown,
job_type=HassJobType.Callback,
) )
return async_track_point_in_utc_time(hass, track_job, point_in_time) return async_track_point_in_utc_time(hass, track_job, point_in_time)
@ -1531,7 +1533,10 @@ def async_track_time_interval(
job_name = f"track time interval {interval} {action}" job_name = f"track time interval {interval} {action}"
interval_listener_job = HassJob( interval_listener_job = HassJob(
interval_listener, job_name, cancel_on_shutdown=cancel_on_shutdown interval_listener,
job_name,
cancel_on_shutdown=cancel_on_shutdown,
job_type=HassJobType.Callback,
) )
remove = async_call_later(hass, interval_seconds, interval_listener_job) remove = async_call_later(hass, interval_seconds, interval_listener_job)
@ -1703,6 +1708,7 @@ def async_track_utc_time_change(
pattern_time_change_listener_job = HassJob( pattern_time_change_listener_job = HassJob(
pattern_time_change_listener, pattern_time_change_listener,
f"time change listener {hour}:{minute}:{second} {action}", f"time change listener {hour}:{minute}:{second} {action}",
job_type=HassJobType.Callback,
) )
time_listener = async_track_point_in_utc_time( time_listener = async_track_point_in_utc_time(
hass, pattern_time_change_listener_job, calculate_next(dt_util.utcnow()) hass, pattern_time_change_listener_job, calculate_next(dt_util.utcnow())

View file

@ -16,7 +16,14 @@ import requests
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import CALLBACK_TYPE, Event, HassJob, HomeAssistant, callback from homeassistant.core import (
CALLBACK_TYPE,
Event,
HassJob,
HassJobType,
HomeAssistant,
callback,
)
from homeassistant.exceptions import ( from homeassistant.exceptions import (
ConfigEntryAuthFailed, ConfigEntryAuthFailed,
ConfigEntryError, ConfigEntryError,
@ -104,7 +111,11 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]):
job_name += f" {name}" job_name += f" {name}"
if entry := self.config_entry: if entry := self.config_entry:
job_name += f" {entry.title} {entry.domain} {entry.entry_id}" job_name += f" {entry.title} {entry.domain} {entry.entry_id}"
self._job = HassJob(self._handle_refresh_interval, job_name) self._job = HassJob(
self._handle_refresh_interval,
job_name,
job_type=HassJobType.Coroutinefunction,
)
self._unsub_refresh: CALLBACK_TYPE | None = None self._unsub_refresh: CALLBACK_TYPE | None = None
self._unsub_shutdown: CALLBACK_TYPE | None = None self._unsub_shutdown: CALLBACK_TYPE | None = None
self._request_refresh_task: asyncio.TimerHandle | None = None self._request_refresh_task: asyncio.TimerHandle | None = None

View file

@ -816,6 +816,16 @@ async def test_eventbus_run_immediately(hass: HomeAssistant) -> None:
unsub() unsub()
async def test_eventbus_run_immediately_not_callback(hass: HomeAssistant) -> None:
"""Test we raise when passing a non-callback with run_immediately."""
def listener(event):
"""Mock listener."""
with pytest.raises(HomeAssistantError):
hass.bus.async_listen("test", listener, run_immediately=True)
async def test_eventbus_unsubscribe_listener(hass: HomeAssistant) -> None: async def test_eventbus_unsubscribe_listener(hass: HomeAssistant) -> None:
"""Test unsubscribe listener from returned function.""" """Test unsubscribe listener from returned function."""
calls = [] calls = []
@ -2534,3 +2544,25 @@ def test_is_callback_check_partial():
assert HassJob(ha.callback(functools.partial(not_callback_func))).job_type == ( assert HassJob(ha.callback(functools.partial(not_callback_func))).job_type == (
ha.HassJobType.Executor ha.HassJobType.Executor
) )
def test_hassjob_passing_job_type():
"""Test passing the job type to HassJob when we already know it."""
@ha.callback
def callback_func():
pass
def not_callback_func():
pass
assert (
HassJob(callback_func, job_type=ha.HassJobType.Callback).job_type
== ha.HassJobType.Callback
)
# We should trust the job_type passed in
assert (
HassJob(not_callback_func, job_type=ha.HassJobType.Callback).job_type
== ha.HassJobType.Callback
)