It only sleeps the one thread but unless you change the Karaf config you only get 5 meaning only 5 rules can run at the same time. When you have lots of Thread::sleeps or long Thread::sleeps you increase the chances that you run out of threads. When you run out of threads, no new rules can start executing until the ones that are blocked by sleeps or long running commands complete and the running rules start to execute. It really becomes a problem when you inadvertently create a feedback loop or other types of choke points.
Symptoms include noticeable latency between the time an event occurs and a rule runs, rules start to run erratically, events are processed out of order, or the rules simply stop executing.
Your pool is 50 so you have a lot larger of a buffer before problems might show up, but if you have a feedback loop or a whole lot of events you could run out even at 50.
I’m not convinced this is the problem, but I’d like to eliminate it.
It largely depends on why you are sleeping and surrounding context.
For example, you could replace “Answering Skype Call” with something like the following:
createTimer(now.plusMillis(1), [ |
sendCommand(TVTools, "KEY_TOOLS")
Thread::sleep(250)
sendCommand(TVKey_Enter, "KEY_ENTER")
Thread::sleep(250) //might need this, might not...
sendCommand(TVKey_DOWN, "KEY_DOWN")
Thread::sleep(250)
sendCommand(TVKey_Enter, "KEY_ENTER")
Thread::sleep(250)
sendCommand(TVKey_Enter, "KEY_ENTER")
])
This will move the sleeps to the Timer threads which have their own pool so you won’t be consuming a Rule thread and allow your Rule to exit immediately.
“Incoming Skype Call, Incoming Skype Call” gives me some concern. If I read this correctly, you can sleep up to six seconds. So, dealing with this section might require a bit more cleverness, though the same trick as above could work.
Personally, I like to have events occur and separate Rules trigger rather than while loops waiting for something to happen. At least you have a max wait in place. But you can shunt the while loop off to a Timer pretty easily with something like:
say("Incoming Skype Call, Incoming Skype Call")
val loopStart = now
var answerCallTimer = createTimer(now.plusMillis(1), [|
if(TVSource_Name.state != "HDMI-CEC" && now.minusSeconds(3).isBefore(loopStart) {
answerCallTimer.reschedule(now.plusMillis(100)) // loop
}
else {
logDebug("Skype", "Answer Exit Loop: TV Source is: " + TVSource_Name.state)
Thread::sleep(2000)
//select video answer button on Skype screen
sendCommand(TVKey_DOWN, "KEY_DOWN")
Thread::sleep(250)
sendCommand(TVKey_DOWN, "KEY_DOWN")
Thread::sleep(250)
sendCommand(TVKey_DOWN, "KEY_DOWN")
Thread::sleep(250)
sendCommand(TVKey_LEFT, "KEY_LEFT")
}
There are other approaches that could help as well but I don’t think we need to go down that path.
One approach that doesn’t really alleviate the sleeps but could make the code a little less prone for unexpected interactions (e.g. you have other Rules that need to send the command to the TV) is Design Pattern: Gate Keeper. Instead of having your Rules send command with sleeps scattered throughout your Rules you send commands to a Design Pattern: Proxy Item and a Rule that triggers and centralizes the Thread::sleeps so you can have multiple Rules issuing commands to the TV at the same time and they won’t stomp on each other. For example:
import java.util.concurrent.locks.ReentrantLock
val lock = new ReentrantLock
rule "TV Command"
when
Item TV_Commands received command
then
lock.lock // Ensures only one instance of the Rule can run at a time
try {
val cmds = TV_Commands.state.toString.split(" ")
cmds.forEach[ cmd |
sendCommand("TV"+cmd.replace("KEY_", "").toLower.charAt(0).toUppercase(), cmd)
Thread::sleep(250)
]
}
catch(Exception e) {
logError("TV", "Error handling TV command: " + e)
}
finally {
lock.unlock
}
end
rule "Skype State Machine"
when
Item TV_Skype_State changed
then
...
if (TV_Status.state != "ONLINE") {
TV_Commands.sendCommand("KEY_TOOLS KEY_ENTER KEY_DOWN KEY_ENTER KEY_ENTER")
}
...
end
NOTE that I’m using Design Pattern: Associated Items to come up with the Item to send the command to based on the cmd string to be sent (it will require normalization of how you do capitalization of your Item names.
You could get even more clever to remove the Thread::sleep in the Rule thread:
import java.util.concurrent.locks.ReentrantLock
import java.util.List
val lock = new ReentrantLock
val List<String> cmds = createArrayList
var cmdsTimer = null
rule "TV Command"
when
Item TV_Commands received command
then
// add the commands to the queue
cmds.addAll(TV_Commands.state.toString.split(" ")
// Start the timer thread to work off the commands
if(cmdsTimer == null) {
cmdsTimer = createTimer(now.plusMillis(1), [ |
try {
val cmds = TV_Commands.state.toString.split(" ")
cmds.forEach[ cmd |
sendCommand("TV"+cmd.replace("KEY_", "").toLower.charAt(0).toUppercase(), cmd)
Thread::sleep(250)
]
}
catch(Exception e) {
logError("TV", "Error handling TV command: " + e)
}
finally {
lock.unlock
}
])
}
end