Starting Point
When migrating my code from DSL to NGRE I was happy to get rid of my lambda-heavy-loaded code and define clear class structures.
A lot of problems I still had with the usage of timers. I have a lot of single-shot timers but also recursive timers. For the single-shot timers, I had situations in which timers for the same purpose were overlapping and creating racing conditions. On the other side, I had the situation that recurring timers were not cleaned up during the rule module reload. The recursive timers were then running in parallel producing unpredictable results.
For example, such a situation happened:
light_timer = Timer(60, lambda: self.turn-light_off())
# on other place in code
# here the previous timer is not stopped
light_timer = Timer(60, lambda: self.turn-light_on())
All around the code the usage pattern was:
if light_timer != None:
light_timer.cancel()
light_timer = Timer(......
Solution
I defined 2 classes: SingleTimer and RecursiveTimer
The usage pattern is:
# define a timer variable as global or class member
timer_var_single = SingleTimer()
timer_var_recursive = RecursiveTimer()
# define cleanup when a module reloaded
def scriptUnloaded():
try:
timer_var_single.cancel()
timer_var_recursive.cancel()
except:
pass
# when and where the timer is needed, assign to the variable the timer again
# give the timer a unique name, timeout value and the function to called
timer_var_single = SingleTimer("MySingleTimer10s", 10, lambda: timer_function())
timer_var_recursive = RecursiveTimer("MyRecursiveTimer60s", 60, lambda: polling_timer_function())
The timer classes maintain a dictionary of all timers based on the timer name. If a name is reused the running timer is canceled and a new is started. There is no need to test on None prior to cancel too.
automation/lib/personal/timers.py
CHANGE:
- removed inheritance between SingleTimer and RecursiveTimer and
- added thread locking
- removed destructor of SingleTimer
- added exception handling
import threading, sys
from threading import Timer, _Timer
from core.log import logging, LOG_PREFIX
log = logging.getLogger(LOG_PREFIX + ".timers.log")
class SingleTimer(object):
__lock = threading.Lock()
def __init__(self, name = None, to = None, fnc = None):
self.__name = name
self.__fnc = fnc
self.__running = False
try:
with SingleTimer.__lock:
if name != None and to != None and fnc != None:
for thread in threading.enumerate():
if isinstance(thread, _Timer) and str(thread.name) == self.__name:
# log.info("*** STOPPING THREAD : {}".format(thread.name))
thread.cancel()
self.__running = True
tmr = Timer(to, lambda: self.__job())
tmr.setName(name)
tmr.start()
except Exception, e:
log.info("SingleTimer __init___() Exception: {}".format(e))
def __job(self):
try:
self.__fnc()
self.__running = False
except Exception, e:
log.info("SingleTimer __job() Exception: {}".format(e))
def isRunning(self):
return self.__running
def cancel(self):
try:
with SingleTimer.__lock:
for thread in threading.enumerate():
if isinstance(thread, _Timer) and str(thread.name) == self.__name:
thread.cancel()
self.__running = False
except Exception, e:
log.info(" SingleTimer cancel() Exception: {}".format(e))
class RecursiveTimer(object):
def __init__(self, name = None, to = None, fnc = None, init_run = False, cycles_to_run = None):
self.__single_tmr = SingleTimer()
self.__cycles = 0
self.__cycles_left = sys.maxint if cycles_to_run == None else cycles_to_run
if name != None and to != None and fnc != None:
self.__first_run = True
self.__fnc = fnc
self.__to = to
self.__name = name
self.__job(init_run)
def cancel(self):
self.__single_tmr.cancel()
def cyclesToRun(self, cycles_to_run):
self.__cycles_left = cycles_to_run
def cyclesLeft(self):
return self.__cycles_left
def cyclesDone(self):
return self.__cycles
def __job(self, init_run = False):
try:
if init_run or not self.__first_run:
self.__cycles = self.__cycles + 1
self.__cycles_left = self.__cycles_left - 1
self.__fnc()
if self.__cycles_left > 0:
self.__first_run = False
self.__single_tmr = SingleTimer(self.__name, self.__to, lambda: self.__job(init_run))
except Exception, e:
log.info(" RecursiveTimer __job() Exception: {}".format(e))
Hello World Example
from core.rules import rule
from core.log import logging, LOG_PREFIX
from core.triggers import StartupTrigger
import threading
from org.python.core import FunctionThread
import imp
import personal.timers
imp.reload(personal.timers)
from personal.timers import RecursiveTimer, SingleTimer
log = logging.getLogger(LOG_PREFIX + ".hello.log")
def scriptUnloaded():
try:
log.info("************* Unload Hello World ********* ")
single_timer.cancel()
recuring_timer.cancel()
except:
pass
single_timer = SingleTimer()
recuring_timer = RecursiveTimer()
@rule("Hello World Timer", description="TimerTest")
class ExampleExtensionRule(object):
def __init__(self):
self.triggers = [StartupTrigger("START").trigger]
global single_timer, recuring_timer
single_timer = SingleTimer("MySingleTimer10s", 10, lambda: self.singleTimerfunction())
recuring_timer= RecursiveTimer("MyRecurringTimer5s", 5, lambda: self.recuringTimerFunction())
def execute(self, module, inputs):
log.info('*** Startup Trigger Fired ***')
def singleTimerfunction(self):
log.info('*** Single Timer after 10s Fired ***')
def recuringTimerFunction(self):
log.info('*** Recurring Timer every 5s Fired***')
In the example, the usage of these classes might look like a big overhead. I can assure you once the code gets more complicated and dynamic this will save a lot of headaches.
Feedback is wormly wellcome