]> git.basschouten.com Git - openhab-addons.git/commitdiff
[jsscripting] Fix memory leak that crashes openHAB (#13824)
authorFlorian Hotze <florianh_dev@icloud.com>
Fri, 2 Dec 2022 23:17:39 +0000 (00:17 +0100)
committerGitHub <noreply@github.com>
Fri, 2 Dec 2022 23:17:39 +0000 (00:17 +0100)
* [jsscripting] Fix memory-leak caused by com.oracle.truffle.host.HostObject

Fixes this memory leak by making the HostAccess for the GraalJSScriptEngine available in a static final variable instead of creating it for each new engine.
Solution proposed in https://github.com/oracle/graaljs/issues/121#issuecomment-690179954.

Sharing a single engine across all Contexts (as proposed in https://github.com/oracle/graaljs/issues/121#issuecomment-880056648) is not possible, because core expects a ScriptEngine.

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
* [jsscripting] Update JavaDoc

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
* [jsscripting] Close `GraalJSScriptEngine` when `OpenhabGraalJSScriptEngine` is closed

My breakpoint inside the close method of GraalJSScriptEngine did not trigger until this change was made.

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

index 704097662a6986b7864e455603d9fa6df8acdaa9..cf1c45464e90a4dd073c931ad60e1bda14485dfb 100644 (file)
@@ -53,12 +53,12 @@ import org.slf4j.LoggerFactory;
 import com.oracle.truffle.js.scriptengine.GraalJSScriptEngine;
 
 /**
- * GraalJS Script Engine implementation
+ * GraalJS ScriptEngine implementation
  *
  * @author Jonathan Gilbert - Initial contribution
  * @author Dan Cunningham - Script injections
  * @author Florian Hotze - Create lock object for multi-thread synchronization; Inject the {@link JSRuntimeFeatures}
- *         into the JS context
+ *         into the JS context; Fix memory leak caused by HostObject by making HostAccess reference static
  */
 public class OpenhabGraalJSScriptEngine
         extends InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable<GraalJSScriptEngine> {
@@ -66,10 +66,24 @@ public class OpenhabGraalJSScriptEngine
     private static final Logger LOGGER = LoggerFactory.getLogger(OpenhabGraalJSScriptEngine.class);
     private static final String GLOBAL_REQUIRE = "require(\"@jsscripting-globals\");";
     private static final String REQUIRE_WRAPPER_NAME = "__wraprequire__";
-    // final CommonJS search path for our library
+    /** Final CommonJS search path for our library */
     private static final Path NODE_DIR = Paths.get("node_modules");
-
-    // shared lock object for synchronization of multi-thread access
+    /** Provides unlimited host access as well as custom translations from JS to Java Objects */
+    private static final HostAccess HOST_ACCESS = HostAccess.newBuilder(HostAccess.ALL)
+            // Translate JS-Joda ZonedDateTime to java.time.ZonedDateTime
+            .targetTypeMapping(Value.class, ZonedDateTime.class, (v) -> v.hasMember("withFixedOffsetZone"), v -> {
+                return ZonedDateTime.parse(v.invokeMember("withFixedOffsetZone").invokeMember("toString").asString());
+            }, HostAccess.TargetMappingPrecedence.LOW)
+
+            // Translate JS-Joda Duration to java.time.Duration
+            .targetTypeMapping(Value.class, Duration.class,
+                    // picking two members to check as Duration has many common function names
+                    (v) -> v.hasMember("minusDuration") && v.hasMember("toNanos"), v -> {
+                        return Duration.ofNanos(v.invokeMember("toNanos").asLong());
+                    }, HostAccess.TargetMappingPrecedence.LOW)
+            .build();
+
+    /** Shared lock object for synchronization of multi-thread access */
     private final Object lock = new Object();
     private final JSRuntimeFeatures jsRuntimeFeatures;
 
@@ -91,27 +105,11 @@ public class OpenhabGraalJSScriptEngine
 
         LOGGER.debug("Initializing GraalJS script engine...");
 
-        // Custom translate JS Objects - > Java Objects
-        HostAccess hostAccess = HostAccess.newBuilder(HostAccess.ALL)
-                // Translate JS-Joda ZonedDateTime to java.time.ZonedDateTime
-                .targetTypeMapping(Value.class, ZonedDateTime.class, (v) -> v.hasMember("withFixedOffsetZone"), v -> {
-                    return ZonedDateTime
-                            .parse(v.invokeMember("withFixedOffsetZone").invokeMember("toString").asString());
-                }, HostAccess.TargetMappingPrecedence.LOW)
-
-                // Translate JS-Joda Duration to java.time.Duration
-                .targetTypeMapping(Value.class, Duration.class,
-                        // picking two members to check as Duration has many common function names
-                        (v) -> v.hasMember("minusDuration") && v.hasMember("toNanos"), v -> {
-                            return Duration.ofNanos(v.invokeMember("toNanos").asLong());
-                        }, HostAccess.TargetMappingPrecedence.LOW)
-                .build();
-
         delegate = GraalJSScriptEngine.create(
                 Engine.newBuilder().allowExperimentalOptions(true).option("engine.WarnInterpreterOnly", "false")
                         .build(),
-                Context.newBuilder("js").allowExperimentalOptions(true).allowAllAccess(true).allowHostAccess(hostAccess)
-                        .option("js.commonjs-require-cwd", JSDependencyTracker.LIB_PATH)
+                Context.newBuilder("js").allowExperimentalOptions(true).allowAllAccess(true)
+                        .allowHostAccess(HOST_ACCESS).option("js.commonjs-require-cwd", JSDependencyTracker.LIB_PATH)
                         .option("js.nashorn-compat", "true") // to ease migration
                         .option("js.ecmascript-version", "2021") // nashorn compat will enforce es5 compatibility, we
                                                                  // want ecma2021
@@ -236,6 +234,7 @@ public class OpenhabGraalJSScriptEngine
     @Override
     public void close() {
         jsRuntimeFeatures.close();
+        delegate.close();
     }
 
     /**