Hi all,
I have created a rule within the openHAB UI that is used by a IKEA Tradfri remote control. It increases/decreases a light’s brightness.
The action part of the rule uses ECMAScript-2021 running on the GraalVM. As I would like to use the main part of the code by multiple remote controls, I put that into an npm module.
The code does what it should do: It increases and decreases the brightness of a connected light item. But the log shows now and then an error message:
2022-01-09 14:49:02.237 [WARN ] [ore.internal.scheduler.SchedulerImpl] - Scheduled job failed and stopped
java.lang.IllegalStateException: Multi threaded access requested by thread Thread[OH-scheduler-38,5,main] but is not allowed for language(s) js.
at com.oracle.truffle.polyglot.PolyglotEngineException.illegalState(PolyglotEngineException.java:129) ~[bundleFile:?]
at com.oracle.truffle.polyglot.PolyglotContextImpl.throwDeniedThreadAccess(PolyglotContextImpl.java:940) ~[bundleFile:?]
at com.oracle.truffle.polyglot.PolyglotContextImpl.checkAllThreadAccesses(PolyglotContextImpl.java:799) ~[bundleFile:?]
at com.oracle.truffle.polyglot.PolyglotContextImpl.enterThreadChanged(PolyglotContextImpl.java:629) ~[bundleFile:?]
at com.oracle.truffle.polyglot.PolyglotEngineImpl.enterCached(PolyglotEngineImpl.java:1885) ~[bundleFile:?]
at com.oracle.truffle.polyglot.HostToGuestRootNode.execute(HostToGuestRootNode.java:112) ~[bundleFile:?]
at com.oracle.truffle.api.impl.DefaultCallTarget.callDirectOrIndirect(DefaultCallTarget.java:85) ~[bundleFile:?]
at com.oracle.truffle.api.impl.DefaultCallTarget.call(DefaultCallTarget.java:102) ~[bundleFile:?]
at com.oracle.truffle.polyglot.PolyglotFunctionProxyHandler.invoke(PolyglotFunctionProxyHandler.java:154) ~[bundleFile:?]
at com.sun.proxy.$Proxy216.apply(Unknown Source) ~[?:?]
at org.openhab.core.model.script.actions.ScriptExecution.lambda$1(ScriptExecution.java:100) ~[bundleFile:?]
at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$12(SchedulerImpl.java:184) ~[?:?]
at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$1(SchedulerImpl.java:87) ~[?:?]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
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:1128) [?:?]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
at java.lang.Thread.run(Thread.java:829) [?:?]
Caused by: com.oracle.truffle.api.TruffleStackTrace$LazyStackTrace
Although the rule works in spite of this warning, I wonder how I can avoid getting it. The warning is mostly written, if the button on the remote control gets pressed multiple times within a short period of time. I am aware that the GraalVM does not allow the multi threaded access.
The code utilises a timer that checks every 100 ms whether a button on the remote control is still pressed (“hold”). If so, it increases/decreases the brightness by a certain brightness step and reschedules the timer, otherwise it gets cancelled. I suppose, the timer is causing the problem but I cannot figure out why that happens.
Please find below the simplified code. “Simplified” means that it does not send any command to the light item, but it also throws the warning message from above:
Rule from the WebUI
configuration: {}
triggers:
- id: "1"
configuration:
itemName: RemoteControl01_Action
type: core.ItemStateChangeTrigger
conditions: []
actions:
- inputs: {}
id: "2"
configuration:
type: application/javascript;version=ECMAScript-2021
script: >
console.log ('rule: start of rule ------------');
var remoteControlAction;
var tt; // timerTest instance
var npmTimerTest;
remoteControlAction = items.getItem (event.itemName).state;
if (remoteControlAction == "brightness_up_hold")
{
npmTimerTest = (npmTimerTest === undefined ? require ('timertest') : npmTimerTest);
if (tt === undefined)
{
tt = new npmTimerTest.TimerTest ();
}
tt.runTimer (this);
}
console.log ('rule: end of rule ------------');
type: script.ScriptAction
npm module: /opt/openhab/conf/automation/js/node_modules/timertest/TimerTest.js
// enable logging by running this command within the karaf console:
// log:set DEBUG org.openhab.automation.script.TimerTest
"use strict";
var zdt = Java.type ("java.time.ZonedDateTime");
//var thr = Java.type ("java.lang.Thread");
var remoteControlHoldActionTimer;
var CLASS_NAME = "TimerTest";
let logger = log (CLASS_NAME);
class TimerTest
{
constructor ()
{
logger.debug ("constructor: Building TimerTest instance.");
this.remoteControlHoldActionTimer = (this.remoteControlHoldActionTimer === undefined) ? null : this.remoteControlHoldActionTimer;
logger.debug ("constructor: TimerTest instance is ready to operate");
};
runTimer = function ()
{
logger.debug ("runTimer: entering");
this.remoteControlHoldActionTimer = actions.ScriptExecution.createTimerWithArgument (
zdt.now ().plusNanos (100 * 1000000),
this,
function (context)
{
logger.debug ("callback: entering vvvvvvv");
logger.debug ("callback: " + items.getItem (event.itemName).state);
if (items.getItem (event.itemName).state == "brightness_up_hold")
{
logger.debug ("callback: reschedule");
try {
context.remoteControlHoldActionTimer.cancel ();
context.remoteControlHoldActionTimer.reschedule (zdt.now().plusNanos (100 * 1000000));
} catch (e) {
logger.debug ("callback: ****** reschedule error = {}", e);
}
}
else
{
logger.debug ("callback: cancel");
try {
context.remoteControlHoldActionTimer.cancel ();
context.remoteControlHoldActionTimer = null;
} catch (e) {
logger.debug ("callback: ****** cancel error = {}", e);
}
}
logger.debug ("callback: leaving ^^^^^^^^^");
}
);
logger.debug ("runTimer: leaving");
}
} // end of class definition
var scriptUnloaded = function () {
console.log ("scriptUnloaded: entering");
console.log ("scriptUnloaded: this.remoteControlHoldActionTimer = " + this.remoteControlHoldActionTimer);
if (!(this.remoteControlHoldActionTimer === undefined))
{
this.remoteControlHoldActionTimer.cancel ();
this.remoteControlHoldActionTimer = null;
}
console.log ("scriptUnloaded: leaving");
}
module.exports = { TimerTest };
npm module: /opt/openhab/conf/automation/js/node_modules/timertest/index.js
console.log ("loaded index.js");
module.exports = require ("./TimerTest");
npm module: /opt/openhab/conf/automation/js/node_modules/timertest/package.json
{
"name": "timertest",
"version": "0.1.0",
"description": "...",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "...",
"license": "ISC"
}
Could someone please point me into the right direction? Why does another thread access the callback(?) function?
Regards,
watt01
Platform information:
- Hardware: Raspberry 4
- OS: Raspian, openHAB running within a docker container
- openHAB version: Snapshot, openHAB 3.3.0, Build #2682