Hi All,
I think I’ve found a bug in HA’s template loop detection code that can cause template entities to miss updates. I’m not sure the best way to fix it. I welcome any thoughts. Pardon the lengthy diagnosis below.
The bug can occur if you have a template entity that references states. For example:
template:
- sensor:
- unique_id: test1
name: test1
state: >
{{ states | selectattr('entity_id','match', 'sensor.*_foo'`) | ... }}
In the above example, depending on the timing of when sensors ending in “foo” update and when the template sensor test1 itself updates, the template sensor may miss updates and fall out of sync. It’s due to a faulty interaction between the rate limiting logic on template re-evaluations and the template loop detection logic. I think if it happens, you’ll see warning in your logs beginning with “Template loop detected” (even though there may be no actual infinite loop).
Background (HA internals)
A tracked template is re-evaluated when any entity it refers to changes state. The re-evaluation code is passed an event object that has the name of the entity that changed state. That event object is used to detect self-reference loops (i.e. if the template is re-evaluating because the template sensor itself changed) and cut them off to prevent infinite loops.
Tracked templates in HA have logic to rate limit re-evaluations The rate limit is implemented via a timer. When a template re-evaluation rate hits the limit, a timer is started. While the timer is running, all state changes are ignored and no re-evaluations are performed. When the timer finishes, a single re-evaluation is done. That single re-evaluation is passed an event object that corresponds to whatever state change happened to cause the rate limit timer to start.
The rate limit for templates referencing states is once every 60 seconds.
EDIT: Also, the logic to track whether a template re-evaluation changed the result (via TrackTemplateResultInfo::_last_result) is calculated in advance of any decision to call subsequent logic to check for loops and eventually update the template entity state.
The bug
Suppose the state change that happens to trip the rate-limit is the template sensor itself changing state. The rate limit timer starts. While the timer’s running say some other state changes happens that will cause the template sensor to change state again when it’s reevaluated. So the timer expires, the template is re-evaluated and changes, triggering the timer to start again (again with the event being that of the template itself changing state). When it expires a second time, the template is re-evaluated and the loop detection code sees two re-evaluations in a row due to the template itself changing, warns of a possible loop, and aborts the re-eval. That effectively causes the template to miss any updates that happened during the timer window.
For reference see where the rate limiter captures the event object: helpers/event.py#1161.
Note, I think the template sensor will correct itself if/when another update comes along that does not cause the sensor to change state.
EDIT : The template sensor may continue to stay out-of-sync despite future state updates because the TrackTemplateResultInfo::_last_result updated to the new template result even though the actual TemplateEntity::_handle_results bailed out early and so never updated the template sensor entity state.
Fixes
One solution is to make TrackTemplateResultInfo::_render_template_if_ready smarter about managing the event object the rate limiter holds on to while a timer is running. If the event object is self-referential and another event occurs while the timer is running which isn’t self-referential, then update the event object stored with the rate limiter to be the non self-referential one.
Thoughts?
Josh