Graal polyglot context suitable for executing javascript code. We run the js in interpreted mode and turn off the warning with the `(option "engine.WarnInterpreterOnly" "false")`. Ideally we would compile the javascript but this is difficult when using the graal ecosystem in a non graal jdk. See https://github.com/oracle/graaljs/blob/master/docs/user/RunOnJDK.md for more information. Javadocs: https://www.graalvm.org/truffle/javadoc/overview-summary.html | (ns metabase.channel.render.js.engine (:require [clojure.core.memoize :as memoize] [clojure.java.io :as io] [metabase.util.i18n :refer [trs]]) (:import (org.graalvm.polyglot Context HostAccess Source Value))) |
(set! *warn-on-reflection* true) | |
Returns a memoizer that is unique to each thread. | (defn threadlocal-fifo-memoizer [thunk threshold] (memoize/fifo (with-meta thunk {::memoize/args-fn (fn [_] [(.getId (Thread/currentThread))])}) :fifo/threshold threshold)) |
Create a new org.graalvm.polyglot.Context suitable to evaluate javascript | (defn context ^Context [] (.. (Context/newBuilder (into-array String ["js"])) ;; https://github.com/oracle/graaljs/blob/master/docs/user/RunOnJDK.md (option "engine.WarnInterpreterOnly" "false") (option "js.intl-402" "true") (allowHostAccess HostAccess/ALL) (allowHostClassLookup (reify java.util.function.Predicate (test [_ _] true))) (out System/out) (err System/err) (allowIO true) (build))) |
Load a string literal source into the js context. | (defn load-js-string [^Context context ^String string-src ^String src-name] (.eval context (.buildLiteral (Source/newBuilder "js" string-src src-name)))) |
Load a resource into the js context | (defn load-resource [^Context context source] (let [resource (io/resource source)] (when (nil? resource) (throw (ex-info (trs "Javascript resource not found: {0}" source) {:source source}))) (.eval context (.build (Source/newBuilder "js" resource))))) |
Executes | (defn execute-fn-name ^Value [^Context context js-fn-name & args] ;; TODO: locking context is not ideal, but contexts are currently being shared with all threads and GraalVM doesn't ;; support concurrent execution for js. ;; There is a couple of idea we can try: ;; - put a thread pool around context initialization ;; - init a new context for each thread (locking context (let [fn-ref (.eval context "js" js-fn-name) args (into-array Object args)] (assert (.canExecute fn-ref) (str "cannot execute " js-fn-name)) (.execute fn-ref args)))) |
fn-ref should be an executable org.graalvm.polyglot.Value return from a js engine. Invoke this function with args. | (defn execute-fn ^Value [^Value fn-ref & args] (assert (.canExecute fn-ref) "cannot execute function reference") (.execute fn-ref (object-array args))) |