Sometimes one wants to make sure that only one instance of a Rule is running at a given time. For example, when a rule is triggered by a Group Item’s receiving updates, the Rule gets triggered more than once per event. Another use is if one needs to implement debouncing where events that take place within a certain time are ignored.
No more than one instance of the rule running at a time
As of OH 3.0, the rule engine for all rules languages no longer allow this to happen. Each rule gets it’s own thread and if a rule that is actively running gets triggered again, the subsequent triggers will be queued up and worked off in order instead of there being two or more instances of the same rule running at the same time.
Therefore rule latching for this use case is no longer required.
I had trouble in the past that the system did not try long enough to get a lock and the rule sometimes did not run. I modified this reentrant locks to try a little longer to get a lock and it seem to have helped. Now I may have been misunderstanding things, but could it be helpful to give more time to try to get a lock? Of course, there are other edge conditions to be considered for every application. But in case it helps anyone, here the code that worked for me
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
rule "My rule"
when
System started or
Item Date received update
then
var gotLock = lock.tryLock(2000, TimeUnit.MILLISECONDS)
try {
if (gotLock) {
// do stuff ( i cut out my rule here for brevity)
}
}
else logInfo("My rule", "re-entry lock actived")
}
finally {
if(gotLock){
lock.unlock()
}
}
end
Obviously the part I wanted to point out is this lock.tryLock(2000, TimeUnit.MILLISECONDS) (and the additionally imported library). I used this when I was on a raspberry and it seemed to make it more reliable;
Part of the problem, as with everything involved with concurrency, is timing or requests and such. The tryLock method that doesn’t take the timeout says
Even when this lock has been set to use a fair ordering policy, a call to tryLock() will immediately acquire the lock if it is available, whether or not other threads are currently waiting for the lock. This “barging” behavior can be useful in certain circumstances, even though it breaks fairness. If you want to honor the fairness setting for this lock, then use tryLock(0, TimeUnit.SECONDS) which is almost equivalent (it also detects interruption).
The section discussing tryLock with the timeout says:
If this lock has been set to use a fair ordering policy then an available lock will not be acquired if any other threads are waiting for the lock.
So, given that, tryLock() causes the thread to jump to the front of the line which could theoretically starve out previous threads waiting to acquire the lock. By adding the timeout what you are doing is forcing the threads to behave nicely and go for the lock in order. I suspect this explains why adding it worked for you. I bet if you changed the 2000 to 0 it would still work as expected.
Thanks for posting! I’m going to change the OP to use the timeout version instead like your example.
You have to be very very careful using locks like this, and it’s one reason why I didn’t show that version above on purpose.
If there is a type error inside the try block the finally block never runs and the lock will never be unlocked. After this rule gets triggered 5 more times all your rules will stop.
If this rule triggers five times in five minutes for some reason, almost your other rules elm so until the first rule exits. And if your rule continues to trigger it will confine to starve all the rest of your rules.
It’s a really bad idea to block rules using a lock like this. Instead, use the Gate Keeper’s Queue example which will let you put the event on a queue that gradually gets worked off without consuming a rule runtime thread for each event awaiting processing.
All I can say is that’s how the underlying Xbase/Xtend language libraries work. I can’t tell you why.
I studier it’s because the language is weakly typed running on a strongly typed language base (Java) when it cannot convert an object’s type to something usable it can’t proceed and execution must ends instead of the exception being thrown.
I have several rules modifing timers from different rules (see example below). To prevent race conditions I used a lock pattern. Sometimes I have the problem that the rules fail because lock is NULL. I think there is a problem with the initialization of the global var. On top of that I read here in the forum that it is no longer recommend to use reentrant locks. Is there a better pattern/solution available? Is there a difference in OH2 vs OH3.
import java.util.concurrent.locks.ReentrantLock
var ReentrantLock lock = new ReentrantLock()
var Timer timer1
rule "Rule 1"
when
Item Item_1 changed from OFF to ON
then
lock.lock()
try {
if (timer1 !== null) {
timer1.cancel()
}
timer1 = createTimer(now.plusSeconds(30), [|
...
])
} finally {
lock.unlock()
}
end
rule "Rule 2"
when
Item Item_1 changed from ON to OFF
then
lock.lock()
try {
if (timer1 !== null) {
timer1.cancel()
}
} finally {
lock.unlock()
}
end
Certainly edge-case timing will have changed in OH3, because you are operating in a different rule engine.
You don’t need any kind of lock for a single UI entered DSL rule - only one “copy” of a rule may execute at a time, further triggers queuing.
I’m not certain that also applies to file created DSL rules - but I expect it does.
So that’s one application for reentrant lock that is no longer needed.
That doesn’t help you much where you are trying to share a lock across multiple rules, though.
Further up this thread is a description of using tryLock with a timeout for better reliability.
There are usually lots of more or less complicated ways to avoid using locks. In the example you’ve given us, you’d simply combine those two rules into one.
Or post 1 gives a timer based approach.
We should probably know a bit more about what you are really doing and what problems it gives you to offer sensible suggestions.
I meant calling other methods on lock would also fail if the instance is null. What do you mean with “import fails I reckon still”. This is the original code:
import java.util.concurrent.locks.ReentrantLock
var ReentrantLock lock = new ReentrantLock()
var Timer einbruchTimer
var Timer alarmAktivierenTimer
...
There’s a limited number of ways for that to happen. You’re going to have to investigate when it happens to track it down further. We’re not told the circumstances; maybe you could test and log alerts if null at each rule run. That would change rule timings by itself of course, but that doesn’t matter in the example shown.