diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index 20b0caec3ca..2e42b28fbd2 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -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.""" diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index dcc05675a01..d9f1b8d9681 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -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 ( diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 31b298330e8..439f154b4af 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -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