JS 2021 Timer restrictions

Hello,

I have some questions which are still not clear after some searching.
I know that there are restrictions to timers because JS does not offer multithreading.

I think this is a major restriction coming from DSL rules and should maybe be made clearer in the docs.

Questions:

  1. I should only use one timer per rule, as it is not possible to have two timers execute at the same time, as the timer uses the rules context and one context is only allowed to run once. Correct?

1a) what happens if a rule gets triggered while already running?

  1. what happens if my rule gets triggered while a timer of the same rule is currently executing?

2a) if there is a problem, what can be done to prevent that?

  1. if I have a function in a .JS file used by multiple rules, that creates a timer, there is no problem because the code can run multiple times in different contexts of the different rules. Correct?

Say I have a motion sensor rule, that needs timers for different things like delayed execution of commands and for delayed off of presence detection.

  1. I saw there are some methods to create and delete rules inside the script. Is it good practice to make functions that create temporary rules just for the sake of having a separate context for delaying a command for a second? Could this not be addressed by the oh helper library?

4a) other methods?

  1. any points I missed?

I really don’t like the possibility of having my rules getting stuck if timing is wrong and having a light that is stuck on or off afterwards.

Thanks for your answers!

1 Like

The current limitation is that there is only one thread per rule. This one thread is shared by that one rule and any timers created in that one rule. So you can have as many timers as you want in any rule, so long as the timers are not scheduled to run at the same time.

There are investigations to address this problem in the add-on (e.g. queueing up Timers so if two are schedule to run at the same time instead they are run in sequence).

The subsequent trigger(s) will queue up and run in sequence once the rule that is already running exits.

I’m not sure. That’s a case that’s really hard to hit on accident. Someone would have to set up a long running timer (e.g. a timer with a function that has a sleep in it) and trigger the rule when you know the timer is running to find out. One of two things are likely to happen. Either the single threaded only allowed exception will occur or the rule trigger will wait for the timer to exit before running the rule.

First verify that it is in fact a problem. If it turns out to be one, file an issue to perhaps bump this up on the priority list for the add-on’s developers. I’m not sure there is much you can do to prevent it beyond making sure your rule scripts and your timers run very fast to minimize the chances that will happen.

If your rule runs in 20 milliseconds and your timer takes only a 20 milliseconds to complete (which is pretty generous, based on times I’ve seen < 10 msecs is pretty typical), you have something like 0.02% chance that you’d have a rule trigger while that timer is running, assuming one timer and one rule and a uniform random start times for both the rule triggers and timer execution. Assuming these rules were being triggered at least once per second and the timer was running at least once per second you’d see a collision once every 105 seconds or so. That’s pretty bad (I’ve no idea if I did the math right, I suspect I got it wrong but it’s close enough for discussion, essentially number of seconds in a year * 0.0002).

But in practice the odds are much less than that because most rules like these run a few dozen times a day and the time that a timer is scheduled to run is almost always based on now plus something. Given that the rule triggers queue up, if you use now plus something for your timers you’ll never have two timers scheduled to run at the exact same time. Even if you have one rule that creates more than one timer per run of the rule, if you use now plus something those two will not be scheduled to run at the same time (though they might be close enough to collide is the plus something is the same).

Thus the chances of having a collision is very low for most use cases of timers. I’d have to bring out my numerical analysis books to remember how to calculate it properly but a gut feeling is once every few years of constant runtime would be the likelihood of a collision for most home automation use cases.

I’ve been using JS Scripting for quite some time now and I’ve only ever seen a collision in a unit test that I wrote where I was not following the above pattern and instead was quite deliberately scheduling timers to run at the same time. I was able to get past that by randomizing the times a little bit and making sure the timers that checked that the other timer ran didn’t collide with any other timer. (In fact it was that experience that revealed this was even a problem in the first place, it was unreported here on the forum or on GitHub prior to that). Given how many people are running with JS Scripting and the number of reports on the forum of people having this problem, I’d venture to say it’s closer to something like once a decade or more.

And for scale and an anecdote, I have Debounce running on around a dozen Items, one Timer per Item (I’ve ported it to JS Scripting). Most of those Items debounce the network status of several machines which are relevant to my home automation. Yesterday I restarted my firewall and all these Items went offline within a few milliseconds of each other. That means a timer was created for each of them to go off within a few milliseconds of each other too. There was no collision. They all ran without problem.

You almost have to go out of your way to make this into a problem.

Correct because each context loads its own copy of the code. There is no interaction between the rules there.

This would be way overcomplicated and probably not actually help you much. But it’s worth a try if you are really concerned or have proven that you actually have this problem.

However, I don’t think it’s worth the massive amounts of extra complications to avoid a problem that you are not even sure you have and even if you do have it would only occur once every several years (assuming that the timer function and rule run reasonably fast, meaning no sleeps or long running commands). Note that rule management from rules in the UI doesn’t quite work right. You can create the rule but you can’t delete it later because of some scope issue with the RuleRegistry or something like that.

NOTE: This is only a problem when the code is actually running. If I schedule a timer to run in five minutes, there’s no problem running that rule or any other timers between now and five minutes from now. It’s only a problem when that five minute timer is actually running the code it’s passed in function. Normally that’s a window where a problem can occur of only a few hundred milliseconds on the outside (again unless there is a sleep or long running command involved).

Also note that if the error does occur, that one run of that one Timer is the only thing that will “stop”. Subsequent runs will work. All your other rules will continue to work.

2 Likes

Thanks for your explanation. I was concerned because I got the multithread-errors a few times while testing, but I was editing the rule and also had some other errors in there maybe.

You opened my eyes that in fact if I have a timer run in 2 minutes I can still create another to run in a second and they’ll not collide.

I guess I will just set everything up and then I’ll see if I run into problems.

That might be a variation of an old DSL problem. If you edit/reload a DSL files based rule after it has spawned a far-future timer, it does NOT kill the timer schedule. That will still go off at the time appointed before the edit, but will usually fail in a big way at execution because the original rule context was destroyed by the reload
An annoyance only when developing a set of rules involving long-lived timers.

I think there are ways to add “upon unload” functions to JS rules to tidy up this kind of straggler.

All the languages except Rules DSL have an onLoad and onUnload (or something like that) you can define for this sort of cleanup. But these can’t be used from the UI, though there have been some recent PRs which might address this.