I’m not sure if this is best posted here, or in migration as it’s only something I’ve been seeing since OH4.
There seems to be some sort of race condition going on with my blockly scripts generating this error:
2023-09-02 13:43:09.676 [WARN ] [e.automation.internal.RuleEngineImpl] - Failed to execute action: script(Multi threaded access requested by thread Thread[OH-rule-UtilityMotion-1,5,main] but is not allowed for language(s) js.)
java.lang.IllegalStateException: Multi threaded access requested by thread Thread[OH-rule-UtilityMotion-1,5,main] but is not allowed for language(s) js.
at com.oracle.truffle.polyglot.PolyglotEngineException.illegalState(PolyglotEngineException.java:129) ~[?:?]
at com.oracle.truffle.polyglot.PolyglotContextImpl.throwDeniedThreadAccess(PolyglotContextImpl.java:1034) ~[?:?]
at com.oracle.truffle.polyglot.PolyglotContextImpl.checkAllThreadAccesses(PolyglotContextImpl.java:893) ~[?:?]
at com.oracle.truffle.polyglot.PolyglotContextImpl.enterThreadChanged(PolyglotContextImpl.java:723) ~[?:?]
at com.oracle.truffle.polyglot.PolyglotEngineImpl.enterCached(PolyglotEngineImpl.java:1991) ~[?:?]
at com.oracle.truffle.polyglot.HostToGuestRootNode.execute(HostToGuestRootNode.java:110) ~[?:?]
at com.oracle.truffle.api.impl.DefaultCallTarget.callDirectOrIndirect(DefaultCallTarget.java:85) ~[?:?]
at com.oracle.truffle.api.impl.DefaultCallTarget.call(DefaultCallTarget.java:102) ~[?:?]
at com.oracle.truffle.polyglot.PolyglotMap.get(PolyglotMap.java:127) ~[?:?]
at com.oracle.truffle.polyglot.PolyglotMap.put(PolyglotMap.java:133) ~[?:?]
at com.oracle.truffle.js.scriptengine.GraalJSBindings.put(GraalJSBindings.java:130) ~[?:?]
at javax.script.SimpleScriptContext.setAttribute(SimpleScriptContext.java:246) ~[java.scripting:?]
at org.openhab.core.automation.module.script.internal.handler.AbstractScriptModuleHandler.setExecutionContext(AbstractScriptModuleHandler.java:135) ~[?:?]
at org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler.lambda$0(ScriptActionHandler.java:69) ~[?:?]
at java.util.Optional.ifPresent(Optional.java:178) ~[?:?]
at org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler.execute(ScriptActionHandler.java:68) ~[?:?]
at org.openhab.core.automation.internal.RuleEngineImpl.executeActions(RuleEngineImpl.java:1188) ~[?:?]
at org.openhab.core.automation.internal.RuleEngineImpl.runNow(RuleEngineImpl.java:1039) ~[?:?]
at org.openhab.core.automation.internal.RuleEngineImpl$1.runNow(RuleEngineImpl.java:248) ~[?:?]
at org.openhab.core.automation.internal.module.handler.RunRuleActionHandler.execute(RunRuleActionHandler.java:106) ~[?:?]
at org.openhab.core.automation.internal.RuleEngineImpl.executeActions(RuleEngineImpl.java:1188) ~[?:?]
at org.openhab.core.automation.internal.RuleEngineImpl.runRule(RuleEngineImpl.java:997) ~[?:?]
at org.openhab.core.automation.internal.TriggerHandlerCallbackImpl$TriggerData.run(TriggerHandlerCallbackImpl.java:87) ~[?:?]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[?:?]
at java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[?:?]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[?:?]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[?:?]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[?:?]
at java.lang.Thread.run(Thread.java:833) ~[?:?]
Caused by: com.oracle.truffle.api.TruffleStackTrace$LazyStackTrace
My approach is, I have a blockly script that will do the grunt work. And then a rule with my triggers and conditions. I use this pattern as I sometimes want to trigger the script from different scripts etc, or have other considerations happening in a rule script first. This has worked well for me, especially in OH3.
I’ve managed to get this problem on a simple implementation, the script is called directly by only one rule, so it’s fairly “clean” with nothing else looking to execute the script.
For reference, this is what I have, which has been migrated to using a looping function call to repeat timers to work around the bug where timers stop rescheduling after two times:
The error occurs only rarely, which smells like some sort of race condition.
I’m unaware of the technicalities of scheduling and threading in OH. When I have a rule that calls a script, does each rule have its own thread, are the scripts called an instance just for it, or do scripts run as a static or singleton type thing? Am I expecting too much from OH with what I’m doing (which seems fairly trivial)?
JS Scripting allows only one thread to access the context of a given Script (Script Action, Script Condition, Timer). Within an individual Script there are locks in place to ensure that that occurs, However, there are no locks in place (not even certain it’s possible) when one rule calls another one. If you have two rules call the the same script at the same time, the second one will generate the multi-threaded access exception.
There was actually a big debate about whether to even allow a rule to call another rule in JS Scripting (and therefore in Blockly too). The fear was exactly this, that people would try to set up libraries using rules that call other rules.I argued for allowing it because it does have a lot of legitimate and useful use cases outside of that one and I’d have hated to lose it. But maybe I was being selfish and it should have been allowed after all.
Ultimately you have a few choices:
live with it failing periodically.
set it up so you can never have two rules try to call the same script at the same time
Move this common code to a library which is where it belongs in the first place. You can create Blockly libraries under Developer Tools → Block Libraries.
3 is the most correct of the answers.
Yes but not in the way that this statement implies. When a rule is triggered if it is already running that trigger will be queued up. Under the normal course of things one instance of a rule can’
t run more than once at a time. However, those checks break down with you have rules that call other rules. There are no locks in place that case and the error gets thrown.
I suspect this could be fixed in core but I don’t know for sure it it makes sense to do so. There is no problem for, for example, two rules to call a Script written in Rules DSL at the same time.
Crudely speaking, I’m assuming something like this is occuring then:
I have a rule that sets off a script, and that rule is then triggered before the script’s previous execution has completed, triggering the multi-threaded exception (because rules can run on different threads). I’m assuming providing the called script has completed the initial run, any timers are then synchronised, as otherwise the “reschedule if retriggered” concept wouldn’t work either?
I guess a work around here would be to use shared value cache to act as a monitor lock? With the rule checking for a lock, and either looping or simply not dispatching the script if it’s already running.
For me, I see there’s a gap between rule and library that scripts fulfil. The logic can be specific enough to warrant a concrete implementation, but also something that may want to be initiated from multiple rules - I know we can use multiple triggers, but there are a few situations where more logic is needed in the rule to decide whether to call.
The downside to me, for libraries is that you have to explicitly script them. Blockly is fantastic for the casual user - I do not have the headspace or time to absorb the object model being used in OH and to know what I need to go hunting around to find where to find items, what functions are available and what not. With blockly I can see what’s supported, and quickly implement something as a hobbiest.
I’m taking from the error, the script is run on its own exclusive thread, if something else calls it from another thread, that’s where the problem occurs. It also seems that called scripts aren’t waited for, and are done in a fire and forget fashion - otherwise the queuing of rules you mention would deal with it.
Could, as a feature change, this be dealt with instead by using a producer/consumer pattern in the dispatch? The execution of a script can be dealt with sequentially by the consumer, which is also pinned to one thread.
Yes, with the understanding that a :script is a :rule and the problem will happen for any rule.
If I understand correctly, yes, access to a single Script Action/Condition’s context is synchronized so only one Thread can access it at a time. If the rule is running or a Timer is running, subsequent triggers or timers have to wait for access to the context.
But when a rule calls another rule that synchronization is bypassed.
I think at best that will reduce the number of occurrences of the exception but I’m not certain that everything that you have access to is synchronized in the ways that it needs to be. You’ll have TOCTOU problems I think.
You. can file an issue. But the challenge is this isn’t something that can be enforced in the JS Scripting add-on and this is not something that is a problem in any of the other rules languages. Should the other languages be limited in this way just because one of the half dozen rules languages has this problem? That’s going to be a hard sell.
Then in OH 3 you must not have been using JS Scripting. Or perhaps the timing was such that you never encountered the error and you were just lucky. I used to get this error routinely in OH 3 and it wasn’t until locks were placed on the context for timers that they went away, and that was implemented in OH 3.3 IIRC, maybe earlier.
It should be happening from a Timer per se. It should only occur if you have one rule calling another one, which should significantly narrow down the places you need to look. Add a log statement before each of those calls and you’ll know exactly where it’s coming from.
But I say “per se” above because you can call another rule from within a timer.
With the locks added I mention in my previous post, this error will not occur:
rule is triggered from an event twice, these are already queued up and worked in order since the Experimental Rules Engine in OH 2.0.
a timer is running, the timer will get exclusive access to the rule’s context while it is running so any subsequent triggers of the rule or other timers that want to run in the same rule will have to wait.
The only time the error will happen is when you have two separate rules that call the same other rule.
@rlkoshak, not fully related to this, but it came to my mind while reading the thread. I really miss a way of having shared code between rules that I can edit using the Main UI. I think that the “Scripts” can be used for that if, instead of “invoke” them, there was a way of “import” them into the rule you are writing, so they behave as if the code was copy pasted in there and run on its execution context.
Do you know if there was already discussions about it? Or maybe it’s a good feature request?
You can import libraries and that is the recommended approach. But you can’t edit libraries in MainUI.
A Script is really just a special case of a rule and a rule consists of more than just the code in a Script Action. It doesn’t really make sense to import a rule into another rule because if that.
Thank you for the explanation, yes cross language support seems impossible, I was thinking more in some rule modifier that allows you to depend on scripts written on the same language, so the engine can keep track of rules depending on the script and invalidate all the contexts on updates. Because the main problem for me when importing file system libraries into the rules was that a library modification didn’t invalidate the rule context (as far as I remember).
Either way, I had these need just a couple of times, not something I want to push on, but it got me curious. Thank you again
Edit: Probably using the scripts as example was a bad idea, as it has its own use case. I was meaning something similar that you can use to maintain a code library through the UI.