Suppress domain and all listeners during template rate limit (#42005)

This commit is contained in:
J. Nick Koston 2020-10-19 03:17:51 -05:00 committed by GitHub
parent 344514601d
commit 3a9b2392f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 87 additions and 2 deletions

View file

@ -1,5 +1,6 @@
"""Helpers for listening to events.""" """Helpers for listening to events."""
import asyncio import asyncio
import copy
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
import functools as ft import functools as ft
@ -820,6 +821,8 @@ class _TrackTemplateResultInfo:
if not _event_triggers_rerender(event, info): if not _event_triggers_rerender(event, info):
return False return False
had_timer = self._rate_limit.async_has_timer(template)
if self._rate_limit.async_schedule_action( if self._rate_limit.async_schedule_action(
template, template,
_rate_limit_for_event(event, info, track_template_), _rate_limit_for_event(event, info, track_template_),
@ -829,7 +832,7 @@ class _TrackTemplateResultInfo:
(track_template_,), (track_template_,),
True, True,
): ):
return False return not had_timer
_LOGGER.debug( _LOGGER.debug(
"Template update %s triggered by event: %s", "Template update %s triggered by event: %s",
@ -893,7 +896,14 @@ class _TrackTemplateResultInfo:
if info_changed: if info_changed:
assert self._track_state_changes assert self._track_state_changes
self._track_state_changes.async_update_listeners( self._track_state_changes.async_update_listeners(
_render_infos_to_track_states(self._info.values()), _render_infos_to_track_states(
[
_suppress_domain_all_in_render_info(self._info[template])
if self._rate_limit.async_has_timer(template)
else self._info[template]
for template in self._info
]
)
) )
_LOGGER.debug( _LOGGER.debug(
"Template group %s listens for %s", "Template group %s listens for %s",
@ -1458,3 +1468,13 @@ def _rate_limit_for_event(
rate_limit: Optional[timedelta] = info.rate_limit rate_limit: Optional[timedelta] = info.rate_limit
return rate_limit return rate_limit
def _suppress_domain_all_in_render_info(render_info: RenderInfo) -> RenderInfo:
"""Remove the domains and all_states from render info during a ratelimit."""
rate_limited_render_info = copy.copy(render_info)
rate_limited_render_info.all_states = False
rate_limited_render_info.all_states_lifecycle = False
rate_limited_render_info.domains = set()
rate_limited_render_info.domains_lifecycle = set()
return rate_limited_render_info

View file

@ -1494,6 +1494,71 @@ async def test_track_template_rate_limit(hass):
assert refresh_runs == [0, 1, 2, 4] assert refresh_runs == [0, 1, 2, 4]
async def test_track_template_rate_limit_suppress_listener(hass):
"""Test template rate limit will suppress the listener during the rate limit."""
template_refresh = Template("{{ states | count }}", hass)
refresh_runs = []
@ha.callback
def refresh_listener(event, updates):
refresh_runs.append(updates.pop().result)
info = async_track_template_result(
hass,
[TrackTemplate(template_refresh, None, timedelta(seconds=0.1))],
refresh_listener,
)
await hass.async_block_till_done()
info.async_refresh()
assert info.listeners == {"all": True, "domains": set(), "entities": set()}
await hass.async_block_till_done()
assert refresh_runs == [0]
hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done()
assert refresh_runs == [0]
info.async_refresh()
assert refresh_runs == [0, 1]
hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done()
# Should be suppressed during the rate limit
assert info.listeners == {"all": False, "domains": set(), "entities": set()}
assert refresh_runs == [0, 1]
next_time = dt_util.utcnow() + timedelta(seconds=0.125)
with patch(
"homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time
):
async_fire_time_changed(hass, next_time)
await hass.async_block_till_done()
# Rate limit released and the all listener returns
assert info.listeners == {"all": True, "domains": set(), "entities": set()}
assert refresh_runs == [0, 1, 2]
hass.states.async_set("sensor.three", "any")
await hass.async_block_till_done()
assert refresh_runs == [0, 1, 2]
hass.states.async_set("sensor.four", "any")
await hass.async_block_till_done()
assert refresh_runs == [0, 1, 2]
# Rate limit hit and the all listener is shut off
assert info.listeners == {"all": False, "domains": set(), "entities": set()}
next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2)
with patch(
"homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time
):
async_fire_time_changed(hass, next_time)
await hass.async_block_till_done()
# Rate limit released and the all listener returns
assert info.listeners == {"all": True, "domains": set(), "entities": set()}
assert refresh_runs == [0, 1, 2, 4]
hass.states.async_set("sensor.five", "any")
await hass.async_block_till_done()
# Rate limit hit and the all listener is shut off
assert info.listeners == {"all": False, "domains": set(), "entities": set()}
assert refresh_runs == [0, 1, 2, 4]
async def test_track_template_rate_limit_five(hass): async def test_track_template_rate_limit_five(hass):
"""Test template rate limit of 5 seconds.""" """Test template rate limit of 5 seconds."""
template_refresh = Template("{{ states | count }}", hass) template_refresh = Template("{{ states | count }}", hass)