[SOLVED] Restart NGRE without to restart whole OH

Tags: #<Tag:0x00007f616ec42e68> #<Tag:0x00007f616ec42cb0> #<Tag:0x00007f616ec42b70>

In my Python rule, I often use recursive timers in the form of (cut down class and rule definition):


class HueGroupMgr(object):

    **** class other stuff ***
 
    def getFromBridge(self):

        *** do some stuff ***

        self.poll_timer = Timer(POLL_INTERVAL, lambda: self.getFromBridge())
        self.poll_timer.start()

    def cleanUpTimers(self):
        if self.poll_timer != None:
            self.poll_timer.cancel()



hueGroupMgr = HueGroupMgr()

@rule("Hue  Group Manager Rule", description="This is an rule for adding groups support for hue groups")
class HueGroupsRule(object):
    def __init__(self):
        self.triggers = hueGroupMgr.getAllTriggers()    

    def execute(self, module, inputs):
        hueGroupMgr.execute(module, inputs)

def scriptUnloaded():
    log.info("************* Unload HueGroupsRule ********* ")              
    hueGroupMgr.cleanUpTimers()   

It seems that although I have a cleaning up timers called by scriptUnloaded sometimes the recursive timer stays active and survives a code reload (saving a new version of code). During developing and debugging this drives me sometimes crazy.
2 Options: Making this code race condition safe, or manually restart the NGRE.

I have some ideas about what could solve the race condition, what I like to ask is how is it possible to restart the NGRE.
I looked at bundle:list but did not find any candidate to restart wit bundle:restart.

I found the reason for my problem:

def scriptUnloaded():

is not called when a script error is detected at the loading of the rule and the error is before loading the decorated class.
e.g.

class HueGroupMgr(object):

    **** class other stuff ***
 
    def getFromBridge(self):

        *** do some stuff ***

        self.poll_timer = Timer(POLL_INTERVAL, lambda: self.getFromBridge())
        self.poll_timer.start()

    def cleanUpTimers(self):
        if self.poll_timer != None:
            self.poll_timer.cancel()



hueGroupMgr = HueGroupMgr()

# a bug here
a = unknownB

@rule("Hue  Group Manager Rule", description="This is an rule for adding groups support for hue groups")
class HueGroupsRule(object):
    def __init__(self):
        self.triggers = hueGroupMgr.getAllTriggers()    

    def execute(self, module, inputs):
        hueGroupMgr.execute(module, inputs)

def scriptUnloaded():
    log.info("************* Unload HueGroupsRule ********* ")              
    hueGroupMgr.cleanUpTimers()   

The solution for scriptUnloaded() was not called when a script error occurs is to move scriptUnloaded() topmost in the file before anything (private library modules or instance declaration) is imported or declared. That makes sure that the scriptUnloaded() is called even a error in code is introduced when reloading script update.

1 Like

From the console…

bundle:restart org.openhab.core.automation

This does not stop background running timers.

Correct, restarting the NGRE bundle will not stop orphaned threads… but you had asked for a way to restart the NGRE without restarting OH. :wink:

Any way to stop orphaned threads, without restarting OH? :slight_smile:

If you use threading.Timer, you could use something like this in a test script to clear all running timers, but this will wipe out the ones that are supposed to be there too! Something similar can probably be done for timers created with createTimer.

import threading
from org.python.core import FunctionThread

for thread in threading.enumerate():
    if isinstance(thread, FunctionThread) and str(thread.state) == "TIMED_WAITING":
        thread.stop()
1 Like

Ran into some problems when trying to implement a foolproof timer class. It seems that the thread delivered by threading.enumerate() as such has a different type depending on how it is called.

I created an example showing that in one case it type FunctionThread and in others it is _Timer:

from threading import Timer
import threading

from core.log import logging, LOG_PREFIX
log = logging.getLogger(LOG_PREFIX + ".hello.log")   

def tmr_fnc():
    log.info(">TRIGGER<")
   
my_thread = Timer(5, lambda: tmr_fnc())
my_thread.setName("MyThread")
my_thread.start()

def show_my_threads():
    for thread in threading.enumerate():
        if thread.name == "MyThread":
            log.info('*** FUNCTION *** thread.name: {} , type(thread): {} '.format(thread.name, type(thread))) 

for thread in threading.enumerate():
    if thread.name == "MyThread":
        log.info('*** PLAIN *** thread.name: {} , type(thread): {} '.format(thread.name, type(thread))) 

show_my_threads()
==> /openhab/userdata/logs/openhab.log <==
2020-04-07 07:00:53.877 [INFO ] [me.core.service.AbstractWatchService] - Loading script 'python/personal/sample.py'
2020-04-07 07:00:53.879 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - Added ScriptEngine for language 'py' with identifier: file:/openhab/conf/automation/jsr223/python/personal/sample.py
2020-04-07 07:00:53.918 [INFO ] [jsr223.jython.hello.log             ] - *** PLAIN *** thread.name: MyThread , type(thread): <type 'org.python.core.FunctionThread'> 
2020-04-07 07:00:53.922 [INFO ] [jsr223.jython.hello.log             ] - *** FUNCTION *** thread.name: MyThread , type(thread): <class 'threading._Timer'> 
2020-04-07 07:00:58.918 [INFO ] [jsr223.jython.hello.log             ] - >TRIGGER<

I know that the use case “PLAIN” is remote, this info just out of curiosity.

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.