Skip to content

Commit ee1b759

Browse files
committed
cleanups and fixed log prefix for UI based rules
1 parent b9912ec commit ee1b759

File tree

4 files changed

+115
-166
lines changed

4 files changed

+115
-166
lines changed

bundles/org.openhab.automation.pythonscripting/src/main/java/org/openhab/automation/pythonscripting/internal/DebuggingPythonScriptEngine.java

-121
This file was deleted.

bundles/org.openhab.automation.pythonscripting/src/main/java/org/openhab/automation/pythonscripting/internal/PythonScriptEngine.java

+95-26
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.time.Instant;
2929
import java.time.ZonedDateTime;
3030
import java.util.ArrayList;
31+
import java.util.Arrays;
3132
import java.util.Collection;
3233
import java.util.HashSet;
3334
import java.util.List;
@@ -39,9 +40,10 @@
3940
import java.util.function.BiFunction;
4041
import java.util.function.Consumer;
4142
import java.util.function.Function;
43+
import java.util.stream.Collectors;
4244

4345
import javax.script.ScriptContext;
44-
import javax.script.ScriptEngine;
46+
import javax.script.ScriptException;
4547

4648
import org.eclipse.jdt.annotation.Nullable;
4749
import org.graalvm.polyglot.Context;
@@ -90,6 +92,10 @@ public class PythonScriptEngine
9092
.get(OpenHAB.getUserDataFolder(), "cache", PythonScriptEngine.class.getPackageName(), "cachedir")
9193
.toString();
9294

95+
private static final int STACK_TRACE_LENGTH = 5;
96+
97+
public static final String LOGGER_INIT_NAME = "__logger_init__";
98+
9399
/** Shared Polyglot {@link Engine} across all instances of {@link PythonScriptEngine} */
94100
private static final Engine ENGINE = Engine.newBuilder().allowExperimentalOptions(true)
95101
.option("engine.WarnInterpreterOnly", "false").build();
@@ -243,26 +249,17 @@ public void checkAccess(Path path, Set<? extends AccessMode> modes,
243249

244250
@Override
245251
protected void beforeInvocation() {
246-
super.beforeInvocation();
247252

248253
lock.lock();
249254
logger.debug("Lock acquired before invocation.");
250255

251-
// must be initialized every time, because of dynamic used attributes like ruleUID
252-
Logger scriptLogger = initScriptLogger(delegate);
253-
scriptOutputStream.setLogger(scriptLogger);
254-
scriptErrorStream.setLogger(scriptLogger);
255-
256256
if (initialized) {
257257
return;
258258
}
259259

260260
logger.debug("Initializing GraalPython script engine...");
261261

262-
ScriptContext ctx = delegate.getContext();
263-
if (ctx == null) {
264-
throw new IllegalStateException("Failed to retrieve script context");
265-
}
262+
ScriptContext ctx = getScriptContext();
266263

267264
// these are added post-construction, so we need to fetch them late
268265
String engineIdentifier = (String) ctx.getAttribute(CONTEXT_KEY_ENGINE_IDENTIFIER);
@@ -298,6 +295,7 @@ protected void beforeInvocation() {
298295
delegate.getPolyglotContext().eval(Source.newBuilder(GraalPythonScriptEngine.LANGUAGE_ID,
299296
wrapperContent, PythonScriptEngineFactory.PYTHON_WRAPPER_FILE_PATH.toString()).build());
300297

298+
// inject scope, Registry and logger
301299
if (injectionEnabled != PythonScriptEngineFactory.INJECTION_DISABLED
302300
&& (ctx.getAttribute("javax.script.filename") == null
303301
|| injectionEnabled == PythonScriptEngineFactory.INJECTION_ENABLED_FOR_ALL_SCRIPTS)) {
@@ -311,9 +309,30 @@ protected void beforeInvocation() {
311309
}
312310
}
313311

312+
// logger initialization, for non file based scripts, has to be delayed, because ruleUID is not available yet
313+
if (ctx.getAttribute("javax.script.filename") == null) {
314+
Runnable wrapperLoggerFn = () -> setScriptLogger();
315+
delegate.getBindings(ScriptContext.ENGINE_SCOPE).put(LOGGER_INIT_NAME, wrapperLoggerFn);
316+
} else {
317+
setScriptLogger();
318+
}
319+
314320
initialized = true;
315321
}
316322

323+
@Override
324+
protected String beforeInvocation(String source) {
325+
String _source = super.beforeInvocation(source);
326+
327+
// Happens for Transform and UI based rules (eval and compile)
328+
// and has to be evaluate every time, because of changing and late injected ruleUID
329+
if (delegate.getBindings(ScriptContext.ENGINE_SCOPE).get(LOGGER_INIT_NAME) != null) {
330+
return LOGGER_INIT_NAME + "()\n" + _source;
331+
}
332+
333+
return _source;
334+
}
335+
317336
@Override
318337
protected Object afterInvocation(Object obj) {
319338
lock.unlock();
@@ -323,20 +342,35 @@ protected Object afterInvocation(Object obj) {
323342

324343
@Override
325344
protected Exception afterThrowsInvocation(Exception e) {
345+
// OPS4J Pax Logging holds a reference to the exception, which causes the OpenhabGraalJSScriptEngine to not be
346+
// removed from heap by garbage collection and causing a memory leak.
347+
// Therefore, don't pass the exceptions itself to the logger, but only their message!
348+
if (e instanceof ScriptException) {
349+
// PolyglotException will always be wrapped into ScriptException and they will be visualized in
350+
// org.openhab.core.automation.module.script.internal.ScriptEngineManagerImpl
351+
if (scriptErrorStream.logger.isDebugEnabled()) {
352+
scriptErrorStream.logger.debug("Failed to execute script (PolyglotException): {}",
353+
stringifyThrowable(e.getCause()));
354+
}
355+
} else if (e.getCause() instanceof IllegalArgumentException) {
356+
scriptErrorStream.logger.error("Failed to execute script (IllegalArgumentException): {}",
357+
stringifyThrowable(e.getCause()));
358+
}
359+
326360
lock.unlock();
361+
327362
return super.afterThrowsInvocation(e);
328363
}
329364

330365
@Override
331366
// collect JSR223 (scope) variables separately, because they are delivered via 'import scope'
332367
public void put(String key, Object value) {
333-
if (key.equals("javax.script.filename") || key.equals("ruleUID")) {
368+
if (key.equals("javax.script.filename")) {
334369
// super.put("__file__", value);
335370
super.put(key, value);
336371
} else {
337-
// use a custom lifecycleTracker to handle dispose hook before polyglot context is closed {@link
338-
// #close()}
339-
// original openHAB {@link LifecycleScriptExtensionProvider}
372+
// use a custom lifecycleTracker to handle dispose hook before polyglot context is closed
373+
// original lifecycleTracker is handling it when polyglot context is already closed
340374
if (key.equals("lifecycleTracker")) {
341375
value = lifecycleTracker;
342376
}
@@ -361,12 +395,24 @@ public void lockInterruptibly() throws InterruptedException {
361395

362396
@Override
363397
public boolean tryLock() {
364-
return lock.tryLock();
398+
boolean acquired = lock.tryLock();
399+
if (acquired) {
400+
logger.debug("Lock acquired.");
401+
} else {
402+
logger.debug("Lock not acquired.");
403+
}
404+
return acquired;
365405
}
366406

367407
@Override
368408
public boolean tryLock(long l, TimeUnit timeUnit) throws InterruptedException {
369-
return lock.tryLock(l, timeUnit);
409+
boolean acquired = lock.tryLock(l, timeUnit);
410+
if (acquired) {
411+
logger.debug("Lock acquired.");
412+
} else {
413+
logger.debug("Lock not acquired.");
414+
}
415+
return acquired;
370416
}
371417

372418
@Override
@@ -386,23 +432,36 @@ public Condition newCondition() {
386432
return lock.newCondition();
387433
}
388434

389-
private static Set<String> transformGraalWrapperSet(Value value) {
390-
// Value raw_value = value.invokeMember("getWrappedSetValues");
391-
Set<String> set = new HashSet<String>();
392-
for (int i = 0; i < value.getArraySize(); ++i) {
393-
Value element = value.getArrayElement(i);
394-
set.add(element.asString());
435+
private void setScriptLogger() {
436+
Logger scriptLogger = initScriptLogger();
437+
scriptOutputStream.setLogger(scriptLogger);
438+
scriptErrorStream.setLogger(scriptLogger);
439+
}
440+
441+
private ScriptContext getScriptContext() {
442+
ScriptContext ctx = delegate.getContext();
443+
if (ctx == null) {
444+
throw new IllegalStateException("Failed to retrieve script context");
395445
}
396-
return set;
446+
return ctx;
447+
}
448+
449+
private String stringifyThrowable(Throwable throwable) {
450+
String message = throwable.getMessage();
451+
StackTraceElement[] stackTraceElements = throwable.getStackTrace();
452+
String stackTrace = Arrays.stream(stackTraceElements).limit(STACK_TRACE_LENGTH)
453+
.map(t -> " at " + t.toString()).collect(Collectors.joining(System.lineSeparator()))
454+
+ System.lineSeparator() + " ... " + stackTraceElements.length + " more";
455+
return (message != null) ? message + System.lineSeparator() + stackTrace : stackTrace;
397456
}
398457

399458
/**
400459
* Initializes the logger.
401460
* This cannot be done on script engine creation because the context variables are not yet initialized.
402461
* Therefore, the logger needs to be initialized on the first use after script engine creation.
403462
*/
404-
public static Logger initScriptLogger(ScriptEngine scriptEngine) {
405-
ScriptContext ctx = scriptEngine.getContext();
463+
private Logger initScriptLogger() {
464+
ScriptContext ctx = getScriptContext();
406465
Object fileName = ctx.getAttribute("javax.script.filename");
407466
Object ruleUID = ctx.getAttribute("ruleUID");
408467
Object ohEngineIdentifier = ctx.getAttribute("oh.engine-identifier");
@@ -421,6 +480,16 @@ public static Logger initScriptLogger(ScriptEngine scriptEngine) {
421480
return LoggerFactory.getLogger("org.openhab.automation.pythonscripting." + identifier);
422481
}
423482

483+
private static Set<String> transformGraalWrapperSet(Value value) {
484+
// Value raw_value = value.invokeMember("getWrappedSetValues");
485+
Set<String> set = new HashSet<String>();
486+
for (int i = 0; i < value.getArraySize(); ++i) {
487+
Value element = value.getArrayElement(i);
488+
set.add(element.asString());
489+
}
490+
return set;
491+
}
492+
424493
private static class LogOutputStream extends OutputStream {
425494
private static final int DEFAULT_BUFFER_LENGTH = 2048;
426495
private static final String LINE_SEPERATOR = System.getProperty("line.separator");

bundles/org.openhab.automation.pythonscripting/src/main/java/org/openhab/automation/pythonscripting/internal/PythonScriptEngineFactory.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,8 @@ public void scopeValues(ScriptEngine scriptEngine, Map<String, Object> scopeValu
131131
if (!scriptTypes.contains(scriptType)) {
132132
return null;
133133
}
134-
// return new PythonScriptEngine(pythonDependencyTracker, cachingEnabled, jythonEmulation);
135-
return new DebuggingPythonScriptEngine<>(new PythonScriptEngine(pythonDependencyTracker, injectionEnabled,
136-
scopeEnabled, cachingEnabled, jythonEmulation));
134+
return new PythonScriptEngine(pythonDependencyTracker, injectionEnabled, scopeEnabled, cachingEnabled,
135+
jythonEmulation);
137136
}
138137

139138
@Override

0 commit comments

Comments
 (0)