]> git.basschouten.com Git - openhab-addons.git/commitdiff
[jsscripting] Fix memory leak on script execution failure (#16578)
authorFlorian Hotze <florianh_dev@icloud.com>
Wed, 27 Mar 2024 20:43:07 +0000 (21:43 +0100)
committerGitHub <noreply@github.com>
Wed, 27 Mar 2024 20:43:07 +0000 (21:43 +0100)
Make engineIdentifier a instance field to ease debugging.

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/DebuggingGraalScriptEngine.java
bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/OpenhabGraalJSScriptEngine.java
bundles/org.openhab.automation.jsscripting/suppressions.properties

index 6342c6848b4d3341233da6d73276bd0c25944af2..508e9cf26aa7f6b6c489e1d338aa09f584cc97dd 100644 (file)
@@ -12,6 +12,9 @@
  */
 package org.openhab.automation.jsscripting.internal;
 
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
 import javax.script.Invocable;
 import javax.script.ScriptContext;
 import javax.script.ScriptEngine;
@@ -26,11 +29,13 @@ import org.slf4j.LoggerFactory;
  * Wraps ScriptEngines provided by Graal to provide error messages and stack traces for scripts.
  *
  * @author Jonathan Gilbert - Initial contribution
+ * @author Florian Hotze - Improve logger name, Fix memory leak caused by exception logging
  */
 class DebuggingGraalScriptEngine<T extends ScriptEngine & Invocable & AutoCloseable>
         extends InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable<T> {
 
     private static final String SCRIPT_TRANSFORMATION_ENGINE_IDENTIFIER = "openhab-transformation-script-";
+    private static final int STACK_TRACE_LENGTH = 5;
 
     private @Nullable Logger logger;
 
@@ -49,15 +54,26 @@ class DebuggingGraalScriptEngine<T extends ScriptEngine & Invocable & AutoClosea
     @Override
     public Exception afterThrowsInvocation(Exception e) {
         Throwable cause = e.getCause();
+        // OPS4J Pax Logging holds a reference to the exception, which causes the OpenhabGraalJSScriptEngine to not be
+        // removed from heap by garbage collection and causing a memory leak.
+        // Therefore, don't pass the exceptions itself to the logger, but only their message!
         if (cause instanceof IllegalArgumentException) {
-            logger.error("Failed to execute script:", e);
-        }
-        if (cause instanceof PolyglotException) {
-            logger.error("Failed to execute script:", cause);
+            logger.error("Failed to execute script: {}", stringifyThrowable(cause));
+        } else if (cause instanceof PolyglotException) {
+            logger.error("Failed to execute script: {}", stringifyThrowable(cause));
         }
         return e;
     }
 
+    private String stringifyThrowable(Throwable throwable) {
+        String message = throwable.getMessage();
+        StackTraceElement[] stackTraceElements = throwable.getStackTrace();
+        String stackTrace = Arrays.stream(stackTraceElements).limit(STACK_TRACE_LENGTH)
+                .map(t -> "        at " + t.toString()).collect(Collectors.joining(System.lineSeparator()))
+                + System.lineSeparator() + "        ... " + stackTraceElements.length + " more";
+        return (message != null) ? message + System.lineSeparator() + stackTrace : stackTrace;
+    }
+
     /**
      * Initializes the logger.
      * This cannot be done on script engine creation because the context variables are not yet initialized.
index ce9450a3a3c64aff663d0cdfc567c1d1f228f35d..e4d324a8a72c5aa261db0b7dfa218b21a597ae55 100644 (file)
@@ -135,6 +135,7 @@ public class OpenhabGraalJSScriptEngine
 
     // these fields start as null because they are populated on first use
     private @Nullable Consumer<String> scriptDependencyListener;
+    private String engineIdentifier; // this field is very helpful for debugging, please do not remove it
 
     private boolean initialized = false;
     private final boolean injectionEnabled;
@@ -242,6 +243,7 @@ public class OpenhabGraalJSScriptEngine
         if (localEngineIdentifier == null) {
             throw new IllegalStateException("Failed to retrieve engine identifier from engine bindings");
         }
+        this.engineIdentifier = localEngineIdentifier;
 
         ScriptExtensionAccessor scriptExtensionAccessor = (ScriptExtensionAccessor) ctx
                 .getAttribute(CONTEXT_KEY_EXTENSION_ACCESSOR);
@@ -250,7 +252,7 @@ public class OpenhabGraalJSScriptEngine
         }
 
         Consumer<String> localScriptDependencyListener = (Consumer<String>) ctx
-                .getAttribute("oh.dependency-listener"/* CONTEXT_KEY_DEPENDENCY_LISTENER */);
+                .getAttribute(CONTEXT_KEY_DEPENDENCY_LISTENER);
         if (localScriptDependencyListener == null) {
             LOGGER.warn(
                     "Failed to retrieve script script dependency listener from engine bindings. Script dependency tracking will be disabled.");
index f57fe0e511d9b7e70f9b0120b02228f90828ef93..38e9aa4cf1896c82f999cde36408fdb3ae94b06f 100644 (file)
@@ -1,2 +1,3 @@
 # Please check here how to add suppressions https://maven.apache.org/plugins/maven-pmd-plugin/examples/violation-exclusions.html
+org.openhab.automation.jsscripting.internal.OpenhabGraalJSScriptEngine=UnusedPrivateField
 org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable=AvoidThrowingNullPointerException,AvoidCatchingNPE