Pattern or Antipattern: Use REST API for callbacks when using (heavy) executeCommandLine

As often pointed out, using executeCommandLine alot might lead to unresponsiveness due to thread pool being exhausted. I wonder if a possible mitigation strategy is to run heavy commands in the background and use the REST API to perform a callback, typically by posting the output from the command into a string item.

Example rule:

rule "Normal verion without callback"
when
    Item TestSwitch received command
then
		val command = "echo Test";
		val result = executeCommandLine(command)
		// Do something with result
end

Callback version:

rule "Test callback"
when
    Item TestSwitch received command
then
    val command = "echo Test"
    // Might wanna tuck this away in an item
    val runWithCallbackCmd = "/etc/openhab2/scripts/runwithcallback.sh"
    executeCommandLine(runWithCallbackCmd + "@@" + command + "@@" + TestString.name)
end

rule "Callback handling"
when
	Item TestString received command
then
	// Do something with state of TestString
end

Where runwithcallback.sh looks something like this:

#!/bin/bash

$1 | curl -X POST --header "Content-Type: text/plain" --header "Accept: application/json" -d @- "http://OPENHAB_IP:8080/rest/items/$2"

#END

It will obviously not make life for the server running openhab much easier (unless the command spends most of its time doing things like waiting for a server response) and you might want to have something which prevents the rule which launches the command from running again before callback (and the error handling to deal with edge cases like if no callback is made for whatever reason).

So, pattern or antipattern?

It’s a good idea. I like it. I involves another bash script though

“Heavy” executeCommandLine can also be “tucked away” in a timer thread and therefore be execute outside the main thread pool

var String command = ""

rule "Normal verion without callback and timer"
when
    Item TestSwitch received command
then
    command = "echo Test"
    createTimer(now, | [
        val result = executeCommandLine(command)
        // Do something with result
    ])
end

The main change is that command need to be a global variable to be visible by the timer lambda.

I like it but be aware that I think the default timeout for an executeCommandLine run in the background like that is five seconds. If the script takes longer than that to run it will be terminated before it can publish the result.

But for scripts that take less than five seconds this sounds like a pretty clever approach.

I’ve recommended this but I’ve never been that comfortable with it. It’s like robbing Peter to pay Paul (there’s another idiom for you :slight_smile: ). It clears up the Rule thread but it uses up a Timer thread and you have even fewer of those (2 compared to the 5 for Rules). And the Timer threads are used to run all Astro triggered Rules, cron triggered Rules, and Timers. So you can encounter the same thread deadlock problem there. But because timer triggered Rules run less frequently perhaps the problem is less likely to occur…