Detect self-referencing loops in template entities and log a warning (#39897)

This commit is contained in:
J. Nick Koston 2020-09-10 13:50:11 -05:00 committed by GitHub
parent bedc1e5672
commit fd8a4182d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 0 deletions

View File

@ -121,6 +121,7 @@ class TemplateEntity(Entity):
"""Template Entity."""
self._template_attrs = {}
self._async_update = None
self._async_update_entity_ids_filter = None
self._attribute_templates = attribute_templates
self._attributes = {}
self._availability_template = availability_template
@ -231,6 +232,9 @@ class TemplateEntity(Entity):
event, update.template, update.last_result, update.result
)
if self._async_update_entity_ids_filter:
self._async_update_entity_ids_filter({self.entity_id})
if self._async_update:
self.async_write_ha_state()
@ -245,8 +249,12 @@ class TemplateEntity(Entity):
)
self.async_on_remove(result_info.async_remove)
result_info.async_refresh()
result_info.async_update_entity_ids_filter({self.entity_id})
self.async_write_ha_state()
self._async_update = result_info.async_refresh
self._async_update_entity_ids_filter = (
result_info.async_update_entity_ids_filter
)
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""

View File

@ -508,6 +508,7 @@ class _TrackTemplateResultInfo:
self._info: Dict[Template, RenderInfo] = {}
self._last_domains: Set = set()
self._last_entities: Set = set()
self._entity_ids_filter: Set = set()
def async_setup(self) -> None:
"""Activation of template tracking."""
@ -659,12 +660,27 @@ class _TrackTemplateResultInfo:
"""Force recalculate the template."""
self._refresh(None)
@callback
def async_update_entity_ids_filter(self, entity_ids: Set) -> None:
"""Update the filtered entity_ids."""
self._entity_ids_filter = entity_ids
@callback
def _refresh(self, event: Optional[Event]) -> None:
entity_id = event and event.data.get(ATTR_ENTITY_ID)
updates = []
info_changed = False
if entity_id and entity_id in self._entity_ids_filter:
# Skip self-referencing updates
for track_template_ in self._track_templates:
_LOGGER.warning(
"Template loop detected while processing event: %s, skipping template render for Template[%s]",
event,
track_template_.template.template,
)
return
for track_template_ in self._track_templates:
template = track_template_.template
if (

View File

@ -758,3 +758,47 @@ async def test_sun_renders_once_per_sensor(hass):
"{{ state_attr('sun.sun', 'elevation') }}",
"{{ state_attr('sun.sun', 'next_rising') }}",
}
async def test_self_referencing_sensor_loop(hass, caplog):
"""Test a self referencing sensor does not loop forever."""
template_str = """
{% for state in states -%}
{{ state.last_updated }}
{%- endfor %}
"""
await async_setup_component(
hass,
"sensor",
{
"sensor": {
"platform": "template",
"sensors": {
"test": {
"value_template": template_str,
},
},
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
value = hass.states.get("sensor.test").state
await hass.async_block_till_done()
value2 = hass.states.get("sensor.test").state
assert value2 == value
await hass.async_block_till_done()
value3 = hass.states.get("sensor.test").state
assert value3 == value2
assert "Template loop detected" in caplog.text