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

pull/41845/head
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."""
import asyncio
import copy
from dataclasses import dataclass
from datetime import datetime, timedelta
import functools as ft
@ -820,6 +821,8 @@ class _TrackTemplateResultInfo:
if not _event_triggers_rerender(event, info):
return False
had_timer = self._rate_limit.async_has_timer(template)
if self._rate_limit.async_schedule_action(
template,
_rate_limit_for_event(event, info, track_template_),
@ -829,7 +832,7 @@ class _TrackTemplateResultInfo:
(track_template_,),
True,
):
return False
return not had_timer
_LOGGER.debug(
"Template update %s triggered by event: %s",
@ -893,7 +896,14 @@ class _TrackTemplateResultInfo:
if info_changed:
assert self._track_state_changes
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(
"Template group %s listens for %s",
@ -1458,3 +1468,13 @@ def _rate_limit_for_event(
rate_limit: Optional[timedelta] = info.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]
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):
"""Test template rate limit of 5 seconds."""
template_refresh = Template("{{ states | count }}", hass)