You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2018/01/13 10:52:47 UTC

[2/8] jena git commit: JENA-1461: Nashorn and threading

JENA-1461: Nashorn and threading


Project: http://git-wip-us.apache.org/repos/asf/jena/repo
Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/a0108d77
Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/a0108d77
Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/a0108d77

Branch: refs/heads/master
Commit: a0108d77c46616e65285f410a2f0f6cdab842c4f
Parents: c4f79ee
Author: Andy Seaborne <an...@apache.org>
Authored: Wed Jan 10 11:21:15 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Wed Jan 10 11:58:26 2018 +0000

----------------------------------------------------------------------
 .../jena/sparql/function/js/EnvJavaScript.java  | 132 ++++++++++---------
 .../sparql/function/js/FunctionJavaScript.java  |   7 +-
 .../jena/sparql/function/js/JSEngine.java       | 109 +++++++++++++++
 jena-arq/testing/ARQ/JS/data.ttl                |   2 +-
 4 files changed, 182 insertions(+), 68 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/a0108d77/jena-arq/src/main/java/org/apache/jena/sparql/function/js/EnvJavaScript.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/EnvJavaScript.java b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/EnvJavaScript.java
index f18d236..167848f 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/EnvJavaScript.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/EnvJavaScript.java
@@ -18,23 +18,13 @@
 
 package org.apache.jena.sparql.function.js;
 
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Paths;
+import javax.script.ScriptException;
 
-import javax.script.*;
-
-import org.apache.jena.atlas.io.IO;
+import org.apache.jena.atlas.lib.Pool;
+import org.apache.jena.atlas.lib.PoolBase;
+import org.apache.jena.atlas.lib.PoolSync;
 import org.apache.jena.query.ARQ;
-import org.apache.jena.riot.RiotNotFoundException;
-import org.apache.jena.sparql.ARQConstants;
-import org.apache.jena.sparql.ARQException;
 import org.apache.jena.sparql.SystemARQ;
-import org.apache.jena.sparql.sse.builders.ExprBuildException;
 import org.apache.jena.sparql.util.Context;
 import org.apache.jena.sparql.util.Symbol;
 
@@ -50,85 +40,99 @@ import org.apache.jena.sparql.util.Symbol;
  * then the string from {@code EnvJavaScript.symJavaScriptLib}.
  */
 public class EnvJavaScript {
-    /** JavaScript as a string value which is evaluated */ 
+    /** JavaScript functions as a string value which is evaluated. */ 
     public static Symbol symJavaScriptLib = SystemARQ.allocSymbol("js-functions");
-    /** JavaScript library in a file as named */ 
+    /** JavaScript library of functions in a file. */ 
     public static Symbol symJavaScriptLibFile = SystemARQ.allocSymbol("js-library");
     
     private final String scriptLib;
     private final String scriptLibFile;
     
-    private final ScriptEngine scriptEngine;
-    private CompiledScript compiledScript;
-    
-    private final Invocable invoc;
-
     public static EnvJavaScript create(Context context) {
         return new EnvJavaScript(context);
     }
     
     private static EnvJavaScript global = null;
     
-    /** Return */
-    public static EnvJavaScript get() { 
+    /**
+     * Return the global {@code EnvJavaScript}. 
+     * Returns null if no JavaScript has been provided.
+     */
+    public static EnvJavaScript get() {
         if ( global == null ) {
-            Context context = ARQ.getContext();
-            if ( context.isDefined(symJavaScriptLib) || context.isDefined(symJavaScriptLibFile) )
-                global = create(ARQ.getContext());
+            synchronized(EnvJavaScript.class) {
+                Context context = ARQ.getContext();
+                if ( context.isDefined(symJavaScriptLib) || context.isDefined(symJavaScriptLibFile) )
+                    global = create(ARQ.getContext());
+            }
         }
         return global ;
     }
-    
-    /** Reset the global EnvJavaScript */
+
+    /** Reset the global {@code EnvJavaScript} based on the system-wide context */
     public static void reset() {
         reset(ARQ.getContext());
     }
 
-    /** Reset the global EnvJavaScript */
+    /** Reset the global {@code EnvJavaScript} */
     public static void reset(Context context) {
         global = create(context);
     }
     
+    // ---- EnvJavaScript Object
+
+    // One script engine per thread, here done by one per usage.
+    // Nashorn script engines are thread safe but the script Bindings} must not be shared.
+    // Direct use of Nashorn, via the protected APIs of jdk.nashorn.api.scripting
+    // are needed to utilize this and having one compiled form and many execution units.
+    // But in Java8 is saving up problems for Java9 and the 
+    // Nashorn subsystem is imporved at Java9 for this use case.
+    // For now, in combination with the implementation of JSEngine,
+    // we keep separate Nashorn script engines. 
+    
+    private Pool<JSEngine> pool = PoolSync.create(new PoolBase<JSEngine>());
+    
     private EnvJavaScript(Context context) {
         this.scriptLib = context.getAsString(symJavaScriptLib);
         this.scriptLibFile = context.getAsString(symJavaScriptLibFile);
-        if ( this.scriptLib == null && this.scriptLibFile == null )
-            throw new ARQException("Both script string and script filename are null"); 
-        ScriptEngineManager manager = new ScriptEngineManager();
-        scriptEngine = manager.getEngineByName("nashorn");
-        // Add function to script engine.
-        invoc = (Invocable)scriptEngine;
-        if ( scriptLibFile != null ) {
-            try {
-                Reader reader = Files.newBufferedReader(Paths.get(scriptLibFile), StandardCharsets.UTF_8);
-                Object x = scriptEngine.eval(reader);
-            }
-            catch (NoSuchFileException | FileNotFoundException ex) {
-                throw new RiotNotFoundException("File: "+scriptLibFile);
-            }
-            catch (IOException ex) { IO.exception(ex); }
-            catch (ScriptException e) {
-                throw new ExprBuildException("Failed to load Javascript", e);
-            }
-        }
-        if ( scriptLib != null ) {
-            try {
-                Object x = scriptEngine.eval(scriptLib);
-            }
-            catch (ScriptException e) {
-                throw new ExprBuildException("Failed to load Javascript", e);
-            }
-        }
-        // Try to call the init function - ignore NoSuchMethodException 
+        // Put one in the pool.
+        pool.put(build());
+    }
+    
+    private JSEngine build() {
+        return new JSEngine(scriptLib, scriptLibFile);
+    }
+    
+    private JSEngine getEngine() {
+        JSEngine engine = pool.get();
+        if ( engine == null )
+            // Which will go into the pool when finished with. 
+            engine = new JSEngine(scriptLib, scriptLibFile);
+        return engine;
+    }
+
+    private EnvJavaScript(String functions, String functionLibFile) { 
+        this.scriptLib = functions;
+        this.scriptLibFile = functionLibFile;
+    }
+
+    public Object call(String functionName, Object[] args) throws NoSuchMethodException, ScriptException {
+        JSEngine engine = getEngine();
         try {
-            invoc.invokeFunction(ARQConstants.JavaScriptInitFunction);
-        } catch (NoSuchMethodException ex) {}
-        catch (ScriptException ex) {
-            throw new ARQException("Failed to call JavaScript initialization function", ex);
+            return engine.call(functionName, args);
+        } finally {
+            pool.put(engine);    
         }
     }
 
-    public Invocable invoc() {
-        return invoc;
-    }
+// ---- ThreadLocal version,
+//    private ThreadLocal<JSEngine> invocable = ThreadLocal.withInitial(()->build());
+//    
+//    private JSEngine build() {
+//        return new JSEngine(scriptLib, scriptLibFile);
+//    } 
+//    public Object call(String functionName, Object[] args) throws NoSuchMethodException, ScriptException {
+//        return invocable.get().call(functionName, args);
+//    }
+    
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/a0108d77/jena-arq/src/main/java/org/apache/jena/sparql/function/js/FunctionJavaScript.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/FunctionJavaScript.java b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/FunctionJavaScript.java
index 4cb1cc0..bd4c25e 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/FunctionJavaScript.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/FunctionJavaScript.java
@@ -48,7 +48,7 @@ import org.apache.jena.sparql.function.FunctionBase;
  * Functions that return null or undefined will resutl in a {@link ExprEvalException}.
  * 
  * @see EnvJavaScript
- * @see NV 
+ * @see NV
  */
 public class FunctionJavaScript extends FunctionBase {
 
@@ -76,9 +76,10 @@ public class FunctionJavaScript extends FunctionBase {
             Object[] a = new Object[args.size()];
             for ( int i = 0 ; i < args.size(); i++ )
                 a[i] = NV.fromNodeValue(args.get(i));
-            Object r = envJS.invoc().invokeFunction(functionName, a);
+            Object r = envJS.call(functionName, a);
             if ( r == null )
-                // Or string "undefined". 
+                // null is used used to signal an ExprEvalException.
+                // NV.throwExprEvalException(....);
                 throw new ExprEvalException(functionName);
             NodeValue nv = NV.toNodeValue(r);
             return nv;

http://git-wip-us.apache.org/repos/asf/jena/blob/a0108d77/jena-arq/src/main/java/org/apache/jena/sparql/function/js/JSEngine.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/JSEngine.java b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/JSEngine.java
new file mode 100644
index 0000000..9084beb
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/JSEngine.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.function.js;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Paths;
+
+import javax.script.*;
+
+import org.apache.jena.atlas.io.IO;
+import org.apache.jena.riot.RiotNotFoundException;
+import org.apache.jena.sparql.ARQConstants;
+import org.apache.jena.sparql.ARQException;
+import org.apache.jena.sparql.sse.builders.ExprBuildException;
+
+/** Abstraction of a <em>per-thread</em> JavaScript execution system */  
+public class JSEngine {
+    private ScriptEngine scriptEngine;
+    private CompiledScript compiledScript;
+    
+    private final Invocable invoc;
+    private String functions;
+
+    private String functionLibFile;
+
+
+    /** Create a {@code JSEngine} from a string. */ 
+    public static JSEngine createFromString(String functions) {
+        return new JSEngine(functions, null);
+    }
+
+    /** Create a {@code JSEngine} from the contents of a file. */ 
+    public static JSEngine createFromFile(String functionLibFile) {
+        return new JSEngine(null, functionLibFile);
+    }
+    
+    /*package*/ JSEngine(String functions, String functionLibFile) { 
+        this.functions = functions;
+        this.functionLibFile = functionLibFile;
+        invoc = build(functions, functionLibFile);
+    }
+
+    private static Invocable build(String functions, String functionLibFile) {
+        if ( functions == null && functionLibFile == null )
+            throw new ARQException("Both script string and script filename are null"); 
+
+        ScriptEngineManager manager = new ScriptEngineManager();
+        ScriptEngine scriptEngine = manager.getEngineByName("nashorn");
+        
+        Invocable invoc = (Invocable)scriptEngine;
+        if ( functionLibFile != null ) {
+            try {
+                Reader reader = Files.newBufferedReader(Paths.get(functionLibFile), StandardCharsets.UTF_8);
+                Object x = scriptEngine.eval(reader);
+            }
+            catch (NoSuchFileException | FileNotFoundException ex) {
+                throw new RiotNotFoundException("File: "+functionLibFile);
+            }
+            catch (IOException ex) { IO.exception(ex); }
+            catch (ScriptException e) {
+                throw new ExprBuildException("Failed to load Javascript", e);
+            }
+        }
+        if ( functions != null ) {
+            try {
+                Object x = scriptEngine.eval(functions);
+            }
+            catch (ScriptException e) {
+                throw new ExprBuildException("Failed to load Javascript", e);
+            }
+        }
+        
+        // Try to call the init function - ignore NoSuchMethodException 
+        try {
+            invoc.invokeFunction(ARQConstants.JavaScriptInitFunction);
+        } catch (NoSuchMethodException ex) {}
+        catch (ScriptException ex) {
+            throw new ARQException("Failed to call JavaScript initialization function", ex);
+        }
+        return invoc;
+    }
+
+    public Object call(String functionName, Object[] args) throws NoSuchMethodException, ScriptException {
+        return invoc.invokeFunction(functionName, args);
+    }
+
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/a0108d77/jena-arq/testing/ARQ/JS/data.ttl
----------------------------------------------------------------------
diff --git a/jena-arq/testing/ARQ/JS/data.ttl b/jena-arq/testing/ARQ/JS/data.ttl
index 12303ce..f99776a 100644
--- a/jena-arq/testing/ARQ/JS/data.ttl
+++ b/jena-arq/testing/ARQ/JS/data.ttl
@@ -1,5 +1,5 @@
 PREFIX : <http://example/>
 
 :s1 :p1 1 .
-:s2 :p2 "one" .
+[] :p2 "one" .