mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
Detect self-referencing loops in template entities and log a warning (#39897)
This commit is contained in:
parent
209cf44e8e
commit
7370b0ffc6
@ -121,6 +121,7 @@ class TemplateEntity(Entity):
|
|||||||
"""Template Entity."""
|
"""Template Entity."""
|
||||||
self._template_attrs = {}
|
self._template_attrs = {}
|
||||||
self._async_update = None
|
self._async_update = None
|
||||||
|
self._async_update_entity_ids_filter = None
|
||||||
self._attribute_templates = attribute_templates
|
self._attribute_templates = attribute_templates
|
||||||
self._attributes = {}
|
self._attributes = {}
|
||||||
self._availability_template = availability_template
|
self._availability_template = availability_template
|
||||||
@ -231,6 +232,9 @@ class TemplateEntity(Entity):
|
|||||||
event, update.template, update.last_result, update.result
|
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:
|
if self._async_update:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@ -245,8 +249,12 @@ class TemplateEntity(Entity):
|
|||||||
)
|
)
|
||||||
self.async_on_remove(result_info.async_remove)
|
self.async_on_remove(result_info.async_remove)
|
||||||
result_info.async_refresh()
|
result_info.async_refresh()
|
||||||
|
result_info.async_update_entity_ids_filter({self.entity_id})
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
self._async_update = result_info.async_refresh
|
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:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Run when entity about to be added to hass."""
|
"""Run when entity about to be added to hass."""
|
||||||
|
@ -508,6 +508,7 @@ class _TrackTemplateResultInfo:
|
|||||||
self._info: Dict[Template, RenderInfo] = {}
|
self._info: Dict[Template, RenderInfo] = {}
|
||||||
self._last_domains: Set = set()
|
self._last_domains: Set = set()
|
||||||
self._last_entities: Set = set()
|
self._last_entities: Set = set()
|
||||||
|
self._entity_ids_filter: Set = set()
|
||||||
|
|
||||||
def async_setup(self) -> None:
|
def async_setup(self) -> None:
|
||||||
"""Activation of template tracking."""
|
"""Activation of template tracking."""
|
||||||
@ -659,12 +660,27 @@ class _TrackTemplateResultInfo:
|
|||||||
"""Force recalculate the template."""
|
"""Force recalculate the template."""
|
||||||
self._refresh(None)
|
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
|
@callback
|
||||||
def _refresh(self, event: Optional[Event]) -> None:
|
def _refresh(self, event: Optional[Event]) -> None:
|
||||||
entity_id = event and event.data.get(ATTR_ENTITY_ID)
|
entity_id = event and event.data.get(ATTR_ENTITY_ID)
|
||||||
updates = []
|
updates = []
|
||||||
info_changed = False
|
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:
|
for track_template_ in self._track_templates:
|
||||||
template = track_template_.template
|
template = track_template_.template
|
||||||
if (
|
if (
|
||||||
|
@ -758,3 +758,47 @@ async def test_sun_renders_once_per_sensor(hass):
|
|||||||
"{{ state_attr('sun.sun', 'elevation') }}",
|
"{{ state_attr('sun.sun', 'elevation') }}",
|
||||||
"{{ state_attr('sun.sun', 'next_rising') }}",
|
"{{ 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…
x
Reference in New Issue
Block a user