fix: ignore stale callbacks from rescheduled timers
- Add generation counters to SmartCondition and EventSensor timers. - Preserve pending changes and the retention period when timers are restarted. - Silently cancel timers that have already expired. - Add regression tests for stale callbacks and expiry during cancellation.
This commit is contained in:
1 parent
1a148d4e3f
commit
54c525c843
4 files changed
+228
-9
No files matched your search
+16
-6
@@ -46,6 +46,7 @@ class Evaluator():
|
||||
self.__state_callbacks_handle = list()
|
||||
self.__condition_state_change_delayed = list()
|
||||
self.__condition_state_change_delayed_cb_handle = None
|
||||
self.__delay_generation = 0
|
||||
|
||||
self.__appdaemon_api = appdaemon_api
|
||||
self.__activated = False
|
||||
@@ -573,6 +574,10 @@ class Evaluator():
|
||||
self.__last_evaluation_result = result
|
||||
|
||||
def __on_condition_state_change_delayed(self, kwargs):
|
||||
# An expired timer may already be queued when a newer change reschedules it.
|
||||
# Ignore it before touching the current timer handle or pending changes.
|
||||
if kwargs.get("generation") != self.__delay_generation:
|
||||
return
|
||||
self.__condition_state_change_delayed_cb_handle = None
|
||||
if len(self.__condition_state_change_delayed) == 0:
|
||||
self.__log(f"Callback has been trigger but self.__condition_state_change_delayed is empty, this shouldn't happen, the callback won't be called", level = "WARNING")
|
||||
@@ -607,13 +612,18 @@ class Evaluator():
|
||||
# self.__update_result()
|
||||
self.__trigger_callbacks(f"Callback trigger by {entity}({old} => {new})")
|
||||
else:
|
||||
if self.__condition_state_change_delayed_cb_handle != None and self.__appdaemon_api.timer_running(self.__condition_state_change_delayed_cb_handle):
|
||||
self.__appdaemon_api.cancel_timer(self.__condition_state_change_delayed_cb_handle)
|
||||
self.__delay_generation += 1
|
||||
handle = self.__condition_state_change_delayed_cb_handle
|
||||
if handle is not None:
|
||||
# Expiry may already have removed the timer from the scheduler.
|
||||
self.__appdaemon_api.cancel_timer(handle, silent=True)
|
||||
self.__condition_state_change_delayed_cb_handle = None
|
||||
|
||||
self.__condition_state_change_delayed.append((entity,old,new,datetime.now()))
|
||||
#self.__log("State changed :" + str(entity) + "(" + str(old) + " => " + str(new) + ") but callback delayed for " + str(self.__callback_delay) + "s")
|
||||
self.__condition_state_change_delayed_cb_handle = self.__appdaemon_api.run_in(self.__on_condition_state_change_delayed, self.__callback_delay)
|
||||
|
||||
self.__condition_state_change_delayed.append((entity,old,new,datetime.now()))
|
||||
self.__condition_state_change_delayed_cb_handle = self.__appdaemon_api.run_in(
|
||||
self.__on_condition_state_change_delayed, self.__callback_delay,
|
||||
generation=self.__delay_generation,
|
||||
)
|
||||
|
||||
def __log(self,message,**kwargs):
|
||||
log_string = self.__log_to_string(message)
|
||||
|
||||
Reference in new issue
Block a user