The explain like I’m 5 explanation is:
Rules run in a parallel fashion. More than one rule can be actively running at the same time. This is a good thing because it allows openHAB to make better use of the computer’s resources.
However, if you have two Rules running at the same time (it could be the same Rule but just triggered twice really fast) these two rules can potentially stomp all over each other. For example consider the following rule:
rule "Rule Stomping"
when
Item MyTriggerItem received update
then
MyActionItem.sendCommand(if(MyActionItem.state == ON) OFF else ON)
Thread::sleep(1000)
MyActionItem.sendCommand(if(MyActionItem.state == OFF) ON else OFF)
end
In the above rule, when MyTriggerItem receives an update, MyActionItem is toggled, sleeps for a second, then toggled again. At the end of the rule MyActionItem will be in the same state as it started. Assuming MyActionItem was ON to start:
Instance 1: MyActionItem == OFF
Instance 1: Sleep for 1 second
Instance 1: MyActionItem == ON
However, if the Rule is triggered and half a second later triggered again, you will have two instances of the Rule running at the same time. So what will happen is, again assuming MyActionItem was ON:
Instance 1: Toggles MyActionItem OFF
Instance 1: Sleeps 1 second
Instance 2: Toggles MyActionItem ON
Instance 2: Sleeps 1 second
Instance 1: Toggles MyActionItem OFF
Instance 2: Toggles MyActionItem ON
So, rather than a nice controlled one second toggling cycle as expected, you end up with two half second toggles.
This may not matter or it may matter a whole bunch. For example, some relays and other devices can be damaged if switched on and off too fast.
If it matters, you can prevent the two instances of the Rules from running at the same time by requiring them to grab a resource (i.e. a lock) before proceeding and releasing that resource when it is done.
If we add a lock to the above example:
include java.util.concurrent.locks.ReentrantLock
val ReentrantLock stompingLock = new ReentrantLock
rule "Rule Stomping"
when
Item MyTriggerItem received update
then
try {
stompingLock.lock()
MyActionItem.sendCommand(if(MyActionItem.state == ON) OFF else ON)
Thread::sleep(1000)
MyActionItem.sendCommand(if(MyActionItem.state == OFF) ON else OFF)
} catch(Throwable t) { }
finally {
stompingLock.unlock()
}
end
In the changes above we created a resource (stompingLock) that each thread must acquire by calling lock() before it will proceed. If another rule called lock() first, the second rule will go to sleep and wait until that first thread calls unlock(). Thus the code between the lock and the unlock can only be executed by one instnace of that Rule at a time. The behavior changes to:
Instance 1: Toggles MyActionItem OFF
Instance 1: Sleeps 1 second
Instance 1: Toggles MyActionItem ON
Instance 2: Toggles MyActionItem OFF
Instance 2: Sleeps 1 second
Instance 2: Toggles MyActionItem ON
As you can see, the two rules now no longer process in parallel but instead in sequence.
The try, catch, finally stuff added is a bit of error handling code. If for some reason there is an error in your code between the lock and the unlock that causes an exception to be thrown and you do not have the try, catch, finally in place then the rule will not have the chance to release the lock by calling unlock and no future triggering of the rule will ever get to run until OH is restarted. By putting the unlock inside the finally clause, even if there is an error the unlock will be called, releasing te lock and allowing future instances of the rule to execute.
This can be used to keep two separate Rules from executing at the same time (just have both rules acquire the same lock before running) or to have rules ignore triggers while another one is running (only execute the logic of the rule is stompingLock.isLocked returns false).