Skip to content

Commit 9ffa412

Browse files
committed
[automation] Synchronize script action/condition execution if engine implements Lock
This moves the locking mechanism added in openhab#4402 to the inheritors of AbstractScriptModuleHandler to synchronize execution context access as well. This fixes the problem thathttps://github.com/openhab/openhab-addons/pull/17510 worked around by using Thread.sleep. Signed-off-by: Florian Hotze <[email protected]>
1 parent 63788b0 commit 9ffa412

File tree

3 files changed

+57
-31
lines changed

3 files changed

+57
-31
lines changed

bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/AbstractScriptModuleHandler.java

+3-17
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
import java.util.Map.Entry;
1818
import java.util.Optional;
1919
import java.util.UUID;
20-
import java.util.concurrent.TimeUnit;
21-
import java.util.concurrent.locks.Lock;
2220

2321
import javax.script.Compilable;
2422
import javax.script.CompiledScript;
@@ -38,6 +36,8 @@
3836

3937
/**
4038
* This is an abstract class that can be used when implementing any module handler that handles scripts.
39+
* <p>
40+
* Remember to implement multi-thread synchronization in the concrete handler if the script engine is not thread-safe!
4141
*
4242
* @author Kai Kreuzer - Initial contribution
4343
* @author Simon Merschjohann - Initial contribution
@@ -212,28 +212,14 @@ protected void resetExecutionContext(ScriptEngine engine, Map<String, ?> context
212212
protected @Nullable Object eval(ScriptEngine engine, String script) {
213213
try {
214214
if (compiledScript.isPresent()) {
215-
if (engine instanceof Lock lock && !lock.tryLock(1, TimeUnit.MINUTES)) {
216-
logger.error("Failed to acquire lock within one minute for script module of rule with UID '{}'",
217-
ruleUID);
218-
return null;
219-
}
220215
logger.debug("Executing pre-compiled script of rule with UID '{}'", ruleUID);
221-
try {
222-
return compiledScript.get().eval(engine.getContext());
223-
} finally { // Make sure that Lock is unlocked regardless of an exception being thrown or not to avoid
224-
// deadlocks
225-
if (engine instanceof Lock lock) {
226-
lock.unlock();
227-
}
228-
}
216+
return compiledScript.get().eval(engine.getContext());
229217
}
230218
logger.debug("Executing script of rule with UID '{}'", ruleUID);
231219
return engine.eval(script);
232220
} catch (ScriptException e) {
233221
logger.error("Script execution of rule with UID '{}' failed: {}", ruleUID, e.getMessage(),
234222
logger.isDebugEnabled() ? e : null);
235-
} catch (InterruptedException e) {
236-
throw new RuntimeException(e);
237223
}
238224
return null;
239225
}

bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptActionHandler.java

+25-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import java.util.HashMap;
1616
import java.util.Map;
17+
import java.util.concurrent.TimeUnit;
18+
import java.util.concurrent.locks.Lock;
1719
import java.util.function.Consumer;
1820

1921
import javax.script.ScriptException;
@@ -31,7 +33,8 @@
3133
*
3234
* @author Kai Kreuzer - Initial contribution
3335
* @author Simon Merschjohann - Initial contribution
34-
* @author Florian Hotze - Add support for script pre-compilation
36+
* @author Florian Hotze - Add support for script pre-compilation, Synchronize script context access if the ScriptEngine
37+
* implements locking
3538
*/
3639
@NonNullByDefault
3740
public class ScriptActionHandler extends AbstractScriptModuleHandler<Action> implements ActionHandler {
@@ -76,10 +79,27 @@ public void compile() throws ScriptException {
7679
}
7780

7881
getScriptEngine().ifPresent(scriptEngine -> {
79-
setExecutionContext(scriptEngine, context);
80-
Object result = eval(scriptEngine, script);
81-
resultMap.put("result", result);
82-
resetExecutionContext(scriptEngine, context);
82+
try {
83+
if (scriptEngine instanceof Lock lock && !lock.tryLock(1, TimeUnit.MINUTES)) {
84+
logger.error(
85+
"Failed to acquire lock within one minute for script module '{}' of rule with UID '{}'",
86+
module.getId(), ruleUID);
87+
return;
88+
}
89+
} catch (InterruptedException e) {
90+
throw new RuntimeException(e);
91+
}
92+
try {
93+
setExecutionContext(scriptEngine, context);
94+
Object result = eval(scriptEngine, script);
95+
resultMap.put("result", result);
96+
resetExecutionContext(scriptEngine, context);
97+
} finally { // Make sure that Lock is unlocked regardless of an exception being thrown or not to avoid
98+
// deadlocks
99+
if (scriptEngine instanceof Lock lock) {
100+
lock.unlock();
101+
}
102+
}
83103
});
84104

85105
return resultMap;

bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java

+29-9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import java.util.Map;
1616
import java.util.Optional;
17+
import java.util.concurrent.TimeUnit;
18+
import java.util.concurrent.locks.Lock;
1719

1820
import javax.script.ScriptEngine;
1921
import javax.script.ScriptException;
@@ -30,7 +32,8 @@
3032
*
3133
* @author Kai Kreuzer - Initial contribution
3234
* @author Simon Merschjohann - Initial contribution
33-
* @author Florian Hotze - Add support for script pre-compilation
35+
* @author Florian Hotze - Add support for script pre-compilation, Synchronize script context access if the ScriptEngine
36+
* implements locking
3437
*/
3538
@NonNullByDefault
3639
public class ScriptConditionHandler extends AbstractScriptModuleHandler<Condition> implements ConditionHandler {
@@ -60,15 +63,32 @@ public boolean isSatisfied(final Map<String, Object> context) {
6063

6164
if (engine.isPresent()) {
6265
ScriptEngine scriptEngine = engine.get();
63-
setExecutionContext(scriptEngine, context);
64-
Object returnVal = eval(scriptEngine, script);
65-
if (returnVal instanceof Boolean boolean1) {
66-
result = boolean1;
67-
} else {
68-
logger.error("Script of rule with UID '{}' did not return a boolean value, but '{}'", ruleUID,
69-
returnVal);
66+
try {
67+
if (scriptEngine instanceof Lock lock && !lock.tryLock(1, TimeUnit.MINUTES)) {
68+
logger.error(
69+
"Failed to acquire lock within one minute for script module '{}' of rule with UID '{}'",
70+
module.getId(), ruleUID);
71+
return result;
72+
}
73+
} catch (InterruptedException e) {
74+
throw new RuntimeException(e);
75+
}
76+
try {
77+
setExecutionContext(scriptEngine, context);
78+
Object returnVal = eval(scriptEngine, script);
79+
if (returnVal instanceof Boolean boolean1) {
80+
result = boolean1;
81+
} else {
82+
logger.error("Script of rule with UID '{}' did not return a boolean value, but '{}'", ruleUID,
83+
returnVal);
84+
}
85+
resetExecutionContext(scriptEngine, context);
86+
} finally { // Make sure that Lock is unlocked regardless of an exception being thrown or not to avoid
87+
// deadlocks
88+
if (scriptEngine instanceof Lock lock) {
89+
lock.unlock();
90+
}
7091
}
71-
resetExecutionContext(scriptEngine, context);
7292
}
7393

7494
return result;

0 commit comments

Comments
 (0)