Rule Initialization - Slow first run after startup workaround

Platform information:

  • Hardware: Pi3B - ARM Cortex-A53 1.2GHz/1GB Ram/120GB SDD over USB
  • OS: OpenHabian 3.2 (Raspian GNU/Linux 9)
  • Java Runtime Environment: Zulu 11 Open JDK 32-bit
  • openHAB version: 3.2

The first time rules execute after a reboot they take much longer than subsequent executions.

On my Pi3B a button press based rule would take 4-5 seconds before taking action the first time the rule was called after a reboot vs less than a second after. From my searching on the subject it looks like this is due to the rules being complied/pulled into memory for the first time. In OH2 it looks like garbage collection used to clear the rules out of memory periodically too. Luckily that doesn't seem to be the case in OH3.2 with Zulu 11. I saw a few suggestions for work arounds, but they were either very specific or used a bunch of sleep() calls. The start-up of OH is so parallelized that you easily wind up race conditions trying to trigger something in. So, I tried a few ideas out and...

I feel like I came up with a decent workaround.

The gist is:
  • I made a generic switch item called SystemStartedStatus.
  • I then made a rule triggered when the SystemStartedStatus changes from null to on or null to off (only at start-up). This rule sets the SystemStartedStatus to ‘OFF’, triggers any time-critical rules, ignoring their conditions, then sets the SystemStartedStatus to ‘ON’, where it remains.
  • The final key is, I have an over-arching if statement that simply logs RuleName Initialized instead of making any actual commands in each of those time-critical rules.
    The result seems pretty repeatable, that the rule fires when the item is initialized and sequentially triggers the rules it runs.

Things that didn't work

Just a mention of other routes I went down to hopefully guide others from falling in the same pitfalls:

  • I tried rule triggering on startlevel (80 & 100 never fired
), but that would conflict with the item getting initialized from time to time. The item would sometimes go from NULL to ON enabling the rule commands.
  • As you can see below, my rules are specific to state changes from OFF to ON and ON to OFF. This prevents inadvertent executions of rules during changes at initializations otherwise it may occur outside of the initialization rule.
  • I also tried triggering all the rules individually, but there was no guarantee the SystemStartedStatus was OFF during various rules. Even if I set the rule to reset the SystemStartedStatus to OFF at an earlier startlevel, it would often occur in the middle of the other rules. Including the OFF - Execute Rules - ON all in one rule seems to have serialized everything.

Here's the main Initialize Rule:

 configuration: {}
triggers:
  - id: "6"
    configuration:
      itemName: SystemStartedStatus
      state: ON
      previousState: "NULL"
    type: core.ItemStateChangeTrigger
  - id: "7"
    configuration:
      itemName: SystemStartedStatus
      state: OFF
      previousState: "NULL"
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      command: OFF
      itemName: SystemStartedStatus
    type: core.ItemCommandAction
  - inputs: {}
    id: "3"
    configuration:
      type: application/javascript
      script: >-2
         var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
        logger.info('System Started Status = OFF')
    type: script.ScriptAction
  - inputs: {}
    id: "4"
    configuration:
      considerConditions: false
      ruleUIDs:
        - 2d93c9ed81
        - e2987b7cf4
        - 8b74458d95
        - 50bbacfc44
        - 87670779f5
        - a4bd443f2f
    type: core.RunRuleAction
  - inputs: {}
    id: "5"
    configuration:
      type: application/javascript
      script: >-2
         var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
        logger.info('System Started Status = ON');  

        events.sendCommand('SystemStartedStatus', 'ON');
    type: script.ScriptAction

And here's an example of one of the triggered rules with the over-arching if statement:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: FamilyKeypad_KeypadButtonD
      state: ON
      previousState: OFF
    type: core.ItemStateChangeTrigger
  - id: "2"
    configuration:
      itemName: FamilyKeypad_KeypadButtonD
      state: OFF
      previousState: ON
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "4"
    configuration:
      type: application/javascript
      script: >
        var thread = Java.type('java.lang.Thread');
        var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
        var Current = itemRegistry.getItem('BookLamp_Switch');
        var SystemStartedStatus = itemRegistry.getItem("SystemStartedStatus");

        if (SystemStartedStatus.state == 'ON') {
          if (Current.state == 'ON') {
            events.sendCommand('BookLamp_Switch', 'OFF');
            logger.info('BookLamp Toggled OFF');}
          else if (Current.state == 'OFF') {
            events.sendCommand('BookLamp_Switch', 'ON');
            logger.info('BookLamp Toggled ON');}}
        else {logger.info('BookLamp Rule Initialized');} 
    type: script.ScriptAction

Lastly, here's the end off my startup log:

You can see the serial execution provided by the rule quite nicely. I don’t know that it will always run after the HABPanel Starts (nearly the end of startup on my system), but its been there 3 times in a row now.

==> /var/log/openhab/openhab.log <==
2022-02-07 00:13:26.413 [INFO ] [ab.ui.habpanel.internal.HABPanelTile] - Started HABPanel at /habpanel
2022-02-07 00:13:27.071 [INFO ] [org.openhab.rule.364348f656         ] - System Started Status = OFF
2022-02-07 00:13:29.929 [INFO ] [org.openhab.rule.2d93c9ed81         ] - EntryLamp Rule Initialized
2022-02-07 00:13:32.207 [INFO ] [org.openhab.rule.e2987b7cf4         ] - FamilyFan Rule Initialized
2022-02-07 00:13:34.428 [INFO ] [org.openhab.rule.8b74458d95         ] - FamilyLamp Rule Initialized
2022-02-07 00:13:36.601 [INFO ] [org.openhab.rule.50bbacfc44         ] - BookLamp Rule Initialized
2022-02-07 00:13:38.711 [INFO ] [org.openhab.rule.87670779f5         ] - Hatch Rule Initialized
2022-02-07 00:13:40.718 [INFO ] [org.openhab.rule.a4bd443f2f         ] - Ceiling Light Rule Initialized
2022-02-07 00:13:42.513 [INFO ] [org.openhab.rule.364348f656         ] - System Started Status = ON

I thought I’d post this here to get some feedback to see if this is something others have already thought of, if there is a more recommended way to do this, or if others find it useful. Having a slow rule after reboot isn’t terrible, but it can be annoying if you have scheduled reboots, work on your rules a lot or if garbage collection gets them. I image it would be very easy to trigger this initialization rule infrequently if I find the delay comes back after a period of time.

I’m a little concerned this will promote XY type of problem handling ignorance which we’re seeing in some posts on in the forum and home automation in general.

Scheduled reboots for example are a very bad idea, they are causing most of the troubles.
It’s XY to work around those troubles like this. The first step to a right solution would be to abandon rebooting [I know some people dislike to hear because they’re very convinced this is a great idea and noone likes being told a great idea he believes in isn’t].
Then again with reboots at most just every few weeks on power outages, delays on first execution are just an occasional nuisance and IMHO not worth putting efforts into to mitigate.

Granted we’re all individuals so ‘nuisance’ is a subjective thing and the root cause of a problem may be harder to find or mitigate.
But the ‘ignore’ strategy has a number of implications such as in your example case the extra miles you need to go everywhere in programming and the additional complexity and issues this will inavoidably introduce.
If your door lock doesn’t work for the 5 minutes it takes to reboot your box, it won’t matter for another 5 secs. It will matter if programming puts your door lock into some permanent state where it doesn’t work, with you standing outside (or - worse - your partner :rofl:).
In Home Automation (or any automation actually), the ‘ignore/avoid’ strategy is never the right one, driving this will backlash when you expect it least. I’m a KISS advocate (Keep It Simple, Stupid).

PS: I had a RPi3 with that issue myself and (while not for this reason) moving to a RPi4 made it disappear.

I’m a little concerned this will promote XY type of problem handling ignorance which we’re seeing in some posts on in the forum and home automation in general.

Scheduled reboots for example are a very bad idea, they are causing most of the troubles.
It’s XY to work around those troubles like this.

My goal really wasn’t to support periodically rebooting. I don’t do so on my OH system. That last sentence was honestly just a quick list for why someone else might want to implement my work around. I see where you’re coming from. I definitely saw a few posts trying to come up with similar solutions for bad reasons, like trying to disable rules during startup that could have simply used better triggers. You don’t want folks to see that sentence and get the expectation that they should be periodically rebooting, or that its a normal to solution to OH problems. I agree they shouldn’t, and its not.

Philosophically, I agree that periodic/scheduled reboots shouldn’t be necessary, but a lot of consumer systems these days have memory leaks and other issues that are not resolved otherwise. Companies/Developers don’t invest the time to solve a slow memory leak that builds up over days/weeks, and don’t provide the tools to users to solve them or make changes for themselves. I’m glad that I can leave my OH3 Pi running for months and not need a reboot, but I couldn’t do that with the windows laptop I’m using to write this, or my android phone. The phone even recommended I setup periodic reboots weekly when I was setting it up. :upside_down_face:

I think you’d agree my post was not an XY type of problem handling. I was very clear my goal was to get rid of the delay the first time my time-critical rules run. I find it a nuisance for my use case, which is a keypad that toggles lights/devices from their current state. We use it as an alternate to using one of the various apps or OH UI. Its especially useful for visiting family members who aren’t plugged into our home’s ecosystem. Being that it’s not used as often, I get a lot of complaints from my partner or by said visiting family that its slow or not working because we hadn’t pressed that particular button since I last tweaked something, lost power, etc. Avoiding those comments made the effort worth while for me.

PS: I had a RPi3 with that issue myself and (while not for this reason) moving to a RPi4 made it disappear.

Good to know. I don’t have any current plans to upgrade my Pi, but it makes sense that better/faster hardware can compile the rules fast enough that there’s no discernable difference.

A better approach would be to put that overarching if statement into a Script Condition. That would simplify the code a bit.

Another approach that a lot of people have used successfully is to create a system startlevel rule to simply disable the rules. Then have another rule that triggers when your Item changes to ON to reenable those rules. That way you’ve centralized this startup logic rather than needing to repeat it all over the place in your rules.

My biggest concern with this specific implementation is that you basically have to add the same logic to every rule (or at least every time sensitive rule). If you can centralize that somehow or, at least pull it out into the condition which you can copy and paste that would be better I think for long term maintenance and legibility.

Off-topic comment; better/faster hardware can bring new timing races that a plodding Pi user never sees.
You still get startup chaos, but taking a different form. For example, system services like rule engine may become available well before external services. As noted already, carefully thinking through rule dependencies is often all that is needed.

1 Like

Do you mean like call a script script from each rule that that checks for the SystemStartupState and exits the rule early? Doesn’t look like return; works for that anymore. That’d just be too easy


Hmm
 I guess maybe I incorrectly assumed that a disabled rule wouldn’t be executed, and therefore not compiled or pulled into memory. Thus it would still run slow the first time it actually executed when enabled. Is that not the case? When I disable a rule in the UI and click run, nothing happens. I enable it, it runs slow, and run a second time its fast.

No, I mean use a script condition in the “But only if” part of the rule.

image

The whole point of Conditions is to define what states must be true before the rule is allowed to run. Though in this case, all you’d really need is an Item Condition so the rule only runs when the SystemStartedStatus Item is ON.

If you had more complicated conditions or wanted to log or something, you can write a script condition.

If it was complicated enough, you could even put that into a library called from the script condition.

But this case isn’t that complicated. All you need is the simple Item Condition on the rules that need to be suppressed while OH is starting up.

That is true, but you can also have rules that call other rules. So instead of disabling them or what ever you can call those rules from a system started rule.

Ultimately, my main concern with this approach, beyond what markus brings up, is that the logic is scattered all over your rules embedded in the script actions. That’s going to be hard to maintain in the long run and hard to remove when/if you move to hardware where this is no longer a problem. As much as possible you want to centralize or at least isolate these sorts of things so you don’t have to go searching through the raw JSONDB to find each and every copy of the logic.

I guess I hadn’t considered you meant those conditions as you pointed out:

Adding a condition based on my SystemStartedStatus item would prevent the rules from running at startup, which would leave them uncompiled and still result in a slow first run. So, while the approach would be more maintainable, it could never actually achieve the goal.

Exactly, that’s what I’m doing to trigger my rules at startup. Their current conditions prevent them from otherwise running at startup, which is why I don’t have to disable them. Using a rule to trigger the other rules is also what enforced the execution of the rules after that startup rule first sets SystemStartedStatus OFF.

I hear your point about maintainability, I would certainly prefer not having to have the extra statement embedded directly in the script of each of the rules. I’m just not currently aware of another way to get the rule compiled and into memory without doing so. Do you have any other suggestions?

Might have to determine that by experiment; does or does not the rule “framework” for trigger and condition also pullin the scripted action section? Bearing in mind a condition may itself be scripted, it might just pull in the whole show anyway on any trigger.

Confirmed with testing that it does not appear to complie the action unless the rule is triggered with conditions met. I setup a simple example and tried with a item condition then with a script condition. I got the same slow first execution after startup complete both times.

1 Like

I now the use case very well.
E.g. after some test an reboots at weekend. The system should react immediately if my wife triggers the “leaving home rule”. A delay on the first run leads to frustration.

Here my solution for ECMA2021.
I added this to each script that should ran fast, even after a reboot.
Because of some trouble with the StartUpLevels I added level 50 and level 100. I’m going to fix that in the future.

configuration: {}
triggers:
  - id: "1"
    configuration:
      startlevel: 50
    type: core.SystemStartlevelTrigger
  - id: "3"
    configuration:
      startlevel: 100
    type: core.SystemStartlevelTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: >+
        'use strict';

        {
          var InitAfterStartupDone; //declared, but undefined 
                    
          if(undefined === InitAfterStartupDone)
          {
            //The first time the Rule is triggerd by: "100 - Startup complete"  
            InitAfterStartupDone = true; //now it is defined and true
            console.log("First run of the rule");
          }
          else
          {
            //All other triggers will led to this code
            console.log("rule already ran, normal code starts here");
          }
        } 


          
    type: script.ScriptAction