Lambda error: Rule 'Lamba Testing': null

  • Platform information:
    • Hardware: Raspberry Pi 3 Model B Rev 1.2
    • OS: Raspbian GNU/Linux 8 (jessie)
    • Java Runtime Environment: don’t know
    • openHAB version: openHAB 2.3.0-1 (Release Build)
  • Issue of the topic: Lambda does not execute (see details below)

I am pulling my hair out (whatever is left…) to get this lambda code working. My problem is a thought understanding of the “types” that OpenHAB is using. I have tried to search for some detailed info, but so far have only found references that lead me into a wild goose chase :sweat:.

Here is the simplified test rule and it’s corresponding error messages in the log:

rule (including the item definition as comment in the rule):

// ********************************************************
// Lambda Test
// ********************************************************
val Functions$Function2 <StringItem, StringItem, Boolean> LambdaTest = [ item1, item2 |

	var int tNumber

	logInfo(RFN, "#*#*#*#*#*#*#*#*#*#* Lambda: entered...")

	if (item2.state == "Family")
	{
		tNumber = Integer::parseInt(item1.state.toString)

		logInfo(RFN, "#*#*#*#*#*#*#*#*#*#* Lambda: tNumber = " + tNumber.toString)
	}

	logInfo(RFN, "#*#*#*#*#*#*#*#*#*#* Lambda: exited...")

	true
]

//********************************************************
// Rule Lamba Testing
// Item definition in .items file:
// String Entered_Code 			"Entered Code"
// String TV_Room 				"Family,Office or Bed"		(gCDcmd)
// ********************************************************
rule "Lamba Testing"
when
	Item Entered_Code changed
then
	logInfo(RFN, "#*#*#*#*#*#*#*#*#*#* Lamba Testing: Entered_Code entry...")
    if(triggeringItem.state == NULL) return;

	logInfo(RFN, "#*#*#*#*#*#*#*#*#*#* Lamba Testing: Trigger Item      :" + triggeringItem)
	logInfo(RFN, "#*#*#*#*#*#*#*#*#*#* Lamba Testing: TV_Room Item      :" + TV_Room)

	// prevent endless loop due to postupdate further down...
	if (triggeringItem.state != "")
	{
		logInfo(RFN, "#*#*#*#*#*#*#*#*#*#* Lamba Testing: Lambda called...")

		LambdaTest.apply(triggeringItem,TV_Room)

		logInfo(RFN, "#*#*#*#*#*#*#*#*#*#* Lamba Testing: Lambda executed...")

		// reset to empty string to allow new (or same) entry via HABpanel
		Entered_Code.postUpdate("")
	}

	logInfo(RFN, "#*#*#*#*#*#*#*#*#*#* Lamba Testing: Entered_Code exit...")
end

The log output is here:

2018-09-29 10:33:48.717 [INFO ] [org.eclipse.smarthome.model.script.ZMOTE          ] - #*#*#*#*#*#*#*#*#*#* Lamba Testing: Entered_Code entry...
2018-09-29 10:33:48.729 [INFO ] [org.eclipse.smarthome.model.script.ZMOTE          ] - #*#*#*#*#*#*#*#*#*#* Lamba Testing: Trigger Item      :Entered_Code (Type=StringItem, State=114, Label=Entered Code, Category=null)
2018-09-29 10:33:48.737 [INFO ] [org.eclipse.smarthome.model.script.ZMOTE          ] - #*#*#*#*#*#*#*#*#*#* Lamba Testing: TV_Room Item      :TV_Room (Type=StringItem, State=Family, Label=Family,Office or Bed, Category=null, Groups=[gCDcmd])
2018-09-29 10:33:48.742 [INFO ] [org.eclipse.smarthome.model.script.ZMOTE          ] - #*#*#*#*#*#*#*#*#*#* Lamba Testing: Lambda called...
2018-09-29 10:33:48.753 [ERROR] [.model.rule.runtime.internal.engine.RuleEngineImpl] - Rule 'Lamba Testing': null

Maybe @rlkoshak can help? (from whom I learned a huge deal! I am ever amazed how much time you spend to teach the community, THANK YOU!)

Thanks in advance,
Al

I don’t see why it isn’t being called… hopefully Rich will have that answer.

But, Rich will say he has largely moved away from Lambdas. I have done the same. There are not thread safe and may cause more problems then they solve…

It seems that it tried to call the lambda, but with error.
The error is too cryptic to understand why.

WRT moving away from Lambdas, what is the alternative and why are they “not thread safe”?

I think I know… But I maybe talking out of my…

You declare:

var int tNumber

An int is a primitive type wich doesn’t have a toString method (I think)
So you need to either do:

var Integer tNumber

Because Integer is a wrapper class for the int which has a toString method

or

logInfo(RFN, "#*#*#*#*#*#*#*#*#*#* Lambda: tNumber = " + String::valueOf(tNumber))

NOT TESTED!!! Pure speculation

There are problems with thread safety and rules and lambdas. In particular, lambdas are not thread safe - so you can write code that assumes any variables are what you expect. Rules also have thread safety problems when using iterators over items or groups. I have not had any luck using locks to manage these problems. In general, the rules DSL, from my experience is very problematic for anything other than simple logic problems using if else expressions. During openhab startup, rules can be executed before variables in that rule file have been instantiated.

First question is is this lambda called from any other Rule? If not then why the lambda in the first place? Just put the code in the Rule.

I can’t answer why the lambda is failing. Does it fail every time it’s called or only during system startup? Are you running on a RPi? Apparently system startup can be quite extended on RPis (20 minutes +have been reported).

My thought follows Vincent’s. tNumber is a primitive which does not have a toString method. Unfortunately almost all errors in lambdas get reported as null exceptions which is one of the reasons that I do not recommend their use except in rare circumstances. Hence my prior question, do you really need a lambda for this?

But to deal with this type problem just define it and put it on the same line and let the Rules DSL deal with the type.

val int tNumber = new BigDecimal(item1.state.toString)

If you do need a lambda, use the simplified syntax:

val LambdaTest = [ StringItem item1, StringItem item2 |
    // code goes here

    // you don't need the true at the end, it will create a Procedures$Procedure2 which don't return anything
]

I agree with most of this, but largely I’ve moved away from them because they just are not necessary except in some relatively rare circumstances.

There are several design patterns that show better alternatives but what is specifically best in your situation depends on what you are trying to accomplish by using a lambda. The Associated Item DP and Separation of Behaviors DP are the two most useful for this, but DPs like Gatekeeper and Generic is Alive are also useful.

As for thread safety, when you create a lambda you are creating an Object. That Object encapsulates the function and the variables used inside that lambda. When you call the lambda from more than one thread you are reusing that same Object and there is nothing inherent in a lambda that separates the two thread’s use of that lambda from each other.

If you need that separation then you should really create a separate instance of the lambda Object every time you run the Rule. But once you do that you may as well just keep the code inside the Rule itself. You can also try to use locks but certain types of errors will cause the rule to exit so you cannot always guarantee that your lock will become unlocked and you will then quickly run out of rule runtime threads and all your Rules will stop executing.

I’m not yet convinced this is a thread safety issue. Unless you have Rules that are adding/removing Items to Groups dynamically the members of a Group never change. If the members are never changing there is no problem with multiple Threads reading from the same List even if it isn’t thread safe.

First and foremost, thanks for the detailed response @rlkoshak!
I’ll answer some of your questions:

First question is is this lambda called from any other Rule?

I had it called from different rules, but over time consolidated into a single rule. So you are correct, I’ll get rid of the Lambda…

Does it fail every time it’s called or only during system startup? Are you running on a RPi? Apparently system startup can be quite extended on RPis (20 minutes +have been reported).

Failed every time, running on RPi, and yes, I’m going for a walk after an update…
In my subsequent investigation I even removed all useful code and just left the logInfo statements to no avail.
At this point I give up and just move away from Lambdas. I can accomplish what I want without them especially in light of what you said about “…reusing that same Object…”. That can easily create situations where multiple calls to the same Lambda can step on each other with random unexpected results.