Javascript timer problem

Thanks for the help. I managed to slog though most of my problems, and I’m left with two remaining.
1: rescheduling the timer results in the called function being out of context.
2: scheduling four timers results in a threading problem.

    hvacGroupName.forEach(v => {                                logger.warn("member of hvac zone: " + v);
      var zoneGroup = items.getItem(v);                         logger.warn(zoneGroup.name);
      zoneGroup.descendents.forEach(control => {                //logger.warn(zoneGroup.name + " controls: " + control.name);
        if (control.name.startsWith("ControlsHeatsetpoint")){
          // for testing initial case
          cache.remove(control.name + "Timer");                 
                                                                logger.warn("control.name: " + control.name);
          var myTimer = cache.get(control.name + "Timer");
          if(myTimer == null){                                  //logger.warn("initial creation of timer");
            myTimer = setTimeout(
              myExpire, 
              time.toZDT(triggeringItem).getMillisFromNow(), 
              control.name);  //logger.warn("initial creation of timer2");
            cache.put(control.name + "Timer", myTimer);         //logger.warn("initial creation of timer3");
          } else {                                              logger.warn("rescheduling timer for " + ts2);
            myTimer.reschedule(time.toZDT(triggeringItem).getMillisFromNow());     logger.warn("rescheduled timer");
          }
        }
      });
    });

myTimer.reschedule(time.toZDT(triggeringItem));
results:

2022-09-02 13:05:01.930 [WARN ] [ore.internal.scheduler.SchedulerImpl] - Scheduled job '<unknown>' failed and stopped
java.lang.IllegalStateException: The Context is already closed.
        at com.oracle.truffle.polyglot.PolyglotEngineException.illegalState(PolyglotEngineException.java:129) ~[?:?]
        at com.oracle.truffle.polyglot.PolyglotContextImpl.checkClosed(PolyglotContextImpl.java:1025) ~[?:?]
        at com.oracle.truffle.polyglot.PolyglotContextImpl.enterThreadChanged(PolyglotContextImpl.java:608) ~[?:?]
...

Surprisingly, I can’t reschedule with another millisecond offset, I have to use ZDT instead
myTimer.reschedule(time.toZDT(triggeringItem).getMillisFromNow());

2022-09-02 13:07:01.213 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID '386125b78d' failed: org.graalvm.polyglot.PolyglotException: TypeError: invokeMember (reschedule) on org.openhab.core.model.script.internal.actions.TimerImpl@1b81a41c failed due to: Cannot convert '720.0'(language: Java, type: java.lang.Double) to Java type 'java.time.ZonedDateTime': Unsupported target type.

I have the problem when I am trying to create four timers. This portion of code is in a loop, and one of my test cases requires four timers to be created, and I am getting the single thread allowed error. I read about this, and am currently searching to find that thread again.
Maybe there’s a better way? I would sure like to use the expire approach, but I can’t set the expire timeout value in a rule.
I used a hashmap of timers in my old Rules DSL, would that be a better approach here?

I am seriously looking at getItem and replaceItem as a solution instead of using timers.
In my rules file, create something like Item immutableSwitch, which has an expire metadata tag, and is in a group, called “ruleGroup”.
Here’s my idea:

  1. When my rule is triggered by a DateTime item that was updated
  2. calculate the time delta from now
  3. do an items.getItem on the immutableSwitch
  4. get its itemConfig, and modify the expire portion of the config.
  5. create a new item with this modified itemConfig.

Now, another rule that listens for changes to “ruleGroup” is triggered. This rule then executes code for the enc of the scheduled meeting for this room that’s in the zone of the airconditioner.

My question is, is the ruleGroup immutable, and will adding a dynamic item in that group going to work like I intend?
Or, should I create as many switches as needed in the items file, and do a replaceItem, instead of a addItem?

I just finished the solution of creating a switch with expire, and it works. There is no threading problem as there is with timers.
To create the switch:

var logger = log("INFO:");
var triggeringName = event.itemName;  //DateTime CalendarEventBeg_CCMain (gCalendarTstat,gHVACZONE_CCMain) 
if (triggeringName.startsWith("CalendarEventEnd")){  
  var triggeringItem = items.getItem(triggeringName);         logger.warn("Triggering calendar event: " + triggeringItem.name);
  if (triggeringItem.state == "UNDEF"){
    logger.warn(triggeringName + " state is undefined, exiting.");
  } else{
    var millisEnd = time.toZDT(triggeringItem).getMillisFromNow();      logger.warn("expire time millisEnd: " + millisEnd);
    var hvacGroupName = triggeringItem.
                      groupNames.
                      filter(s => s.startsWith("gHVACZONE"));   logger.warn("member of hvac zones: " + hvacGroupName.join(" "));
    hvacGroupName.forEach(v => {                                logger.warn("member of hvac zone: " + v);
      var zoneGroup = items.getItem(v);                         logger.warn(zoneGroup.name);
      zoneGroup.descendents.forEach(control => {                //logger.warn(zoneGroup.name + " controls: " + control.name);
        if (control.name.startsWith("ControlsHeatsetpoint")){
          var sName = control.name+"_Timer";
          items.removeItem(sName);
          items.addItem({
            type: 'Switch',
            name: sName,
            groups: ['switchGroup'],
            metadata: {
              expire: '1s,command=OFF',
              stateDescription: {
                config: {
                  pattern: '%s'
                }
              }
            }
          });
          logger.warn("MySwitch: " + items.getItem(sName).getMetadataValue("expire"));
          items.getItem(sName).sendCommand('ON');
        }
      });
    });
  }
}

Then the rule that is triggered when any member of switchGroup changes from ON to OFF:

var logger = log("INFO:");
var triggeringName = event.itemName;  //DateTime CalendarEventBeg_CCMain (gCalendarTstat,gHVACZONE_CCMain) 
var triggeringItem = items.getItem(triggeringName);         logger.warn("TIMER EXPIRED IN GROUP: " + triggeringItem.name);
var indexOfFirst = triggeringName.indexOf("_Timer");
var controlName = triggeringName.substring(0, indexOfFirst);logger.warn("controlName: " + controlName);
items.getItem(controlName).sendCommand(62);

I find jrule very simple to use timers.
Depends on your level of programming as well, but switching to java I could simplify my timer handling a lot. See example 8

Is there still the single thread limitation in JRule? If one created multiple timers, would it throw an exception?
This is very interesting to me though, because strong type parameters is currently my biggest hurdle with JavaScript. Can VSCode be used in this workflow and have all of the features I enjoy in a regular java project?

I’ve not seen that error in this context. Are you sure you didn’t edit/reload the role that created the timers in the first place? If not this needs further exploration.

Reach rule only gets one thread. The timers created by that rule share that same thread. If a timer goes off at the same time the rule or another timer is running, you’ll see that error.

There is an issue open to address that but it’s not yet fixed. I’m also going to build into my opengab-rules-tools some offsets and book keeping so that it prevents timers from going off at the same time like that in many cases

I’m the mean time, you can see one way to do that yourself in the Open Door done templare in the marketplace.

Another alternative is to use something like the Gatekeeper DP (also available as a library in openhab-rules-tools) to schedule your actions. That will prevent them from running on to of each other.

Another approach could be to use a looping timer (also available in openhab-rules-tools) and a queue to work off in that.

There are lots of ways around this until a permanent fix gets implemented in oh core or the add-on.

I wonder is by the time the timer runs off the local variables have gone away it been replaced. I’ve little experience with setTimeout so if have to experiment to see what’s going on here.

I usually use function generators with timers to prevent this problem. Instead of passing the function, call a function with all the variables needed as arguments, and that function returns the function called by the timer. That fixed the variables so subsequent rule triggers do not mess with then.

Expire is Item metadata so theoretically you could change that on a rule. But there is no event when you change the metadata so you’d probably have to update the item to trigger the expire with the new value.

It depends on a lot of factors. If using for based rules on must language that won’t work because a member of trigger unrolls to individual triggers, one for each member, at least time. Chasing the Groups members after the fact may not pick up the changes until the rule is reloaded.

In the UI it might work differently, over never tried it.

No, number of threads is configurable. The important difference is using async non blocking completable futures for timer handling.

/s