mirror of https://github.com/home-assistant/core
Detect self-referencing loops in template entities and log a warning (#39897)
This commit is contained in:
parent
bedc1e5672
commit
fd8a4182d9
|
@ -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."""
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue