8046202: Make persistent code store more flexible
authorhannesw
Fri, 19 Sep 2014 13:13:20 +0200
changeset 26764 c777787a937d
parent 26668 1b1ec4291abc
child 26765 97501edd2979
8046202: Make persistent code store more flexible Reviewed-by: lagergren, sundar
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/CodeStore.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Context.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/FunctionInitializer.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/StoredScript.java
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/CodeStore.java	Wed Jul 05 20:01:50 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/CodeStore.java	Fri Sep 19 13:13:20 2014 +0200
@@ -34,51 +34,42 @@
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.ServiceLoader;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.runtime.logging.DebugLogger;
 import jdk.nashorn.internal.runtime.logging.Loggable;
 import jdk.nashorn.internal.runtime.logging.Logger;
+import jdk.nashorn.internal.runtime.options.Options;
 
 /**
  * A code cache for persistent caching of compiled scripts.
  */
 @Logger(name="codestore")
-final class CodeStore implements Loggable {
+public abstract class CodeStore implements Loggable {
 
-    private final File dir;
-    private final int minSize;
-    private final DebugLogger log;
+    /**
+     * Permission needed to provide a CodeStore instance via ServiceLoader.
+     */
+    public final static String NASHORN_PROVIDE_CODE_STORE = "nashorn.provideCodeStore";
 
-    // Default minimum size for storing a compiled script class
-    private final static int DEFAULT_MIN_SIZE = 1000;
+    private DebugLogger log;
 
     /**
      * Constructor
-     * @throws IOException
      */
-    public CodeStore(final Context context, final String path) throws IOException {
-        this(context, path, DEFAULT_MIN_SIZE);
-    }
-
-    /**
-     * Constructor
-     * @param path directory to store code in
-     * @param minSize minimum file size for caching scripts
-     * @throws IOException
-     */
-    public CodeStore(final Context context, final String path, final int minSize) throws IOException {
-        this.dir = checkDirectory(path);
-        this.minSize = minSize;
-        this.log = initLogger(context);
+    protected CodeStore() {
     }
 
     @Override
     public DebugLogger initLogger(final Context context) {
-         return context.getLogger(getClass());
+        log = context.getLogger(getClass());
+        return log;
     }
 
     @Override
@@ -86,29 +77,101 @@
         return log;
     }
 
-    private static File checkDirectory(final String path) throws IOException {
+    /**
+     * Returns a new code store instance.
+     *
+     * @param context the current context
+     * @return The instance
+     * @throws IOException If an error occurs
+     */
+    public static CodeStore newCodeStore(final Context context) throws IOException {
+        final Class<CodeStore> baseClass = CodeStore.class;
         try {
-            return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
-                @Override
-                public File run() throws IOException {
-                    final File dir = new File(path).getAbsoluteFile();
-                    if (!dir.exists() && !dir.mkdirs()) {
-                        throw new IOException("Could not create directory: " + dir.getPath());
-                    } else if (!dir.isDirectory()) {
-                        throw new IOException("Not a directory: " + dir.getPath());
-                    } else if (!dir.canRead() || !dir.canWrite()) {
-                        throw new IOException("Directory not readable or writable: " + dir.getPath());
-                    }
-                    return dir;
-                }
-            });
-        } catch (final PrivilegedActionException e) {
-            throw (IOException) e.getException();
+            // security check first
+            final SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(new RuntimePermission(NASHORN_PROVIDE_CODE_STORE));
+            }
+            final ServiceLoader<CodeStore> services = ServiceLoader.load(baseClass);
+            final Iterator<CodeStore> iterator = services.iterator();
+            if (iterator.hasNext()) {
+                final CodeStore store = iterator.next();
+                store.initLogger(context).info("using code store provider ", store.getClass().getCanonicalName());
+                return store;
+            }
+        } catch (final AccessControlException e) {
+            context.getLogger(CodeStore.class).warning("failed to load code store provider ", e);
         }
+        final CodeStore store = new DirectoryCodeStore();
+        store.initLogger(context);
+        return store;
     }
 
-    private File getCacheFile(final Source source, final String functionKey) {
-        return new File(dir, source.getDigest() + '-' + functionKey);
+
+    /**
+     * Store a compiled script in the cache.
+     *
+     * @param functionKey   the function key
+     * @param source        the source
+     * @param mainClassName the main class name
+     * @param classBytes    a map of class bytes
+     * @param initializers  the function initializers
+     * @param constants     the constants array
+     * @param compilationId the compilation id
+     */
+    public StoredScript store(final String functionKey,
+                              final Source source,
+                              final String mainClassName,
+                              final Map<String, byte[]> classBytes,
+                              final Map<Integer, FunctionInitializer> initializers,
+                              final Object[] constants,
+                              final int compilationId) {
+        return store(functionKey, source, storedScriptFor(source, mainClassName, classBytes, initializers, constants, compilationId));
+    }
+
+    /**
+     * Stores a compiled script.
+     *
+     * @param functionKey the function key
+     * @param source the source
+     * @param script The compiled script
+     * @return The compiled script or {@code null} if not stored
+     */
+    public abstract StoredScript store(final String functionKey,
+                                       final Source source,
+                                       final StoredScript script);
+
+    /**
+     * Return a compiled script from the cache, or null if it isn't found.
+     *
+     * @param source      the source
+     * @param functionKey the function key
+     * @return the stored script or null
+     */
+    public abstract StoredScript load(final Source source, final String functionKey);
+
+    /**
+     * Returns a new StoredScript instance.
+     *
+     * @param mainClassName the main class name
+     * @param classBytes a map of class bytes
+     * @param initializers function initializers
+     * @param constants the constants array
+     * @param compilationId the compilation id
+     * @return The compiled script
+     */
+    public StoredScript storedScriptFor(final Source source, final String mainClassName,
+                                        final Map<String, byte[]> classBytes,
+                                        final Map<Integer, FunctionInitializer> initializers,
+                                        final Object[] constants, final int compilationId) {
+        for (final Object constant : constants) {
+            // Make sure all constant data is serializable
+            if (!(constant instanceof Serializable)) {
+                getLogger().warning("cannot store ", source, " non serializable constant ", constant);
+                return null;
+            }
+        }
+        return new StoredScript(compilationId, mainClassName, classBytes, initializers, constants);
     }
 
     /**
@@ -129,77 +192,130 @@
     }
 
     /**
-     * Return a compiled script from the cache, or null if it isn't found.
-     *
-     * @param source the source
-     * @param functionKey the function key
-     * @return the stored script or null
+     * A store using a file system directory.
      */
-    public StoredScript loadScript(final Source source, final String functionKey) {
-        if (source.getLength() < minSize) {
-            return null;
+    public static class DirectoryCodeStore extends CodeStore {
+
+        // Default minimum size for storing a compiled script class
+        private final static int DEFAULT_MIN_SIZE = 1000;
+
+        private final File dir;
+        private final boolean readOnly;
+        private final int minSize;
+
+        /**
+         * Constructor
+         *
+         * @throws IOException
+         */
+        public DirectoryCodeStore() throws IOException {
+            this(Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE);
         }
 
-        final File file = getCacheFile(source, functionKey);
-
-        try {
-            return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() {
-                @Override
-                public StoredScript run() throws IOException, ClassNotFoundException {
-                    if (!file.exists()) {
-                        return null;
-                    }
-                    try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
-                        final StoredScript storedScript = (StoredScript) in.readObject();
-                        getLogger().info("loaded ", source, "-", functionKey);
-                        return storedScript;
-                    }
-                }
-            });
-        } catch (final PrivilegedActionException e) {
-            getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException());
-            return null;
+        /**
+         * Constructor
+         *
+         * @param path    directory to store code in
+         * @param minSize minimum file size for caching scripts
+         * @throws IOException
+         */
+        public DirectoryCodeStore(final String path, final boolean readOnly, final int minSize) throws IOException {
+            this.dir = checkDirectory(path, readOnly);
+            this.readOnly = readOnly;
+            this.minSize = minSize;
         }
-    }
 
-    /**
-     * Store a compiled script in the cache.
-     *
-     * @param functionKey the function key
-     * @param source the source
-     * @param mainClassName the main class name
-     * @param classBytes a map of class bytes
-     * @param constants the constants array
-     */
-    public void storeScript(final String functionKey, final Source source, final String mainClassName, final Map<String, byte[]> classBytes,
-                          final Map<Integer, FunctionInitializer> initializers, final Object[] constants, final int compilationId) {
-        if (source.getLength() < minSize) {
-            return;
-        }
-        for (final Object constant : constants) {
-            // Make sure all constant data is serializable
-            if (! (constant instanceof Serializable)) {
-                getLogger().warning("cannot store ", source, " non serializable constant ", constant);
-                return;
+        private static File checkDirectory(final String path, final boolean readOnly) throws IOException {
+            try {
+                return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
+                    @Override
+                    public File run() throws IOException {
+                        final File dir = new File(path).getAbsoluteFile();
+                        if (readOnly) {
+                            if (!dir.exists() || !dir.isDirectory()) {
+                                throw new IOException("Not a directory: " + dir.getPath());
+                            } else if (!dir.canRead()) {
+                                throw new IOException("Directory not readable: " + dir.getPath());
+                            }
+                        } else if (!dir.exists() && !dir.mkdirs()) {
+                            throw new IOException("Could not create directory: " + dir.getPath());
+                        } else if (!dir.isDirectory()) {
+                            throw new IOException("Not a directory: " + dir.getPath());
+                        } else if (!dir.canRead() || !dir.canWrite()) {
+                            throw new IOException("Directory not readable or writable: " + dir.getPath());
+                        }
+                        return dir;
+                    }
+                });
+            } catch (final PrivilegedActionException e) {
+                throw (IOException) e.getException();
             }
         }
 
-        final File file = getCacheFile(source, functionKey);
-        final StoredScript script = new StoredScript(compilationId, mainClassName, classBytes, initializers, constants);
+        @Override
+        public StoredScript load(final Source source, final String functionKey) {
+            if (source.getLength() < minSize) {
+                return null;
+            }
+
+            final File file = getCacheFile(source, functionKey);
+
+            try {
+                return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() {
+                    @Override
+                    public StoredScript run() throws IOException, ClassNotFoundException {
+                        if (!file.exists()) {
+                            return null;
+                        }
+                        try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
+                            final StoredScript storedScript = (StoredScript) in.readObject();
+                            getLogger().info("loaded ", source, "-", functionKey);
+                            return storedScript;
+                        }
+                    }
+                });
+            } catch (final PrivilegedActionException e) {
+                getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException());
+                return null;
+            }
+        }
 
-        try {
-            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
-                @Override
-                public Void run() throws IOException {
-                    try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
-                        out.writeObject(script);
+        @Override
+        public StoredScript store(final String functionKey, final Source source, final StoredScript script) {
+            if (readOnly || script == null || belowThreshold(source)) {
+                return null;
+            }
+
+            final File file = getCacheFile(source, functionKey);
+
+            try {
+                return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() {
+                    @Override
+                    public StoredScript run() throws IOException {
+                        try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
+                            out.writeObject(script);
+                        }
+                        getLogger().info("stored ", source, "-", functionKey);
+                        return script;
                     }
-                    getLogger().info("stored ", source, "-", functionKey);
-                    return null;
-                }
-            });
-        } catch (final PrivilegedActionException e) {
-            getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException());
+                });
+            } catch (final PrivilegedActionException e) {
+                getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException());
+                return null;
+            }
+        }
+
+
+        private File getCacheFile(final Source source, final String functionKey) {
+            return new File(dir, source.getDigest() + '-' + functionKey);
+        }
+
+        private boolean belowThreshold(final Source source) {
+            if (source.getLength() < minSize) {
+                getLogger().info("below size threshold ", source);
+                return true;
+            }
+            return false;
         }
     }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Context.java	Wed Jul 05 20:01:50 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Context.java	Fri Sep 19 13:13:20 2014 +0200
@@ -29,6 +29,7 @@
 import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION;
 import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE;
+import static jdk.nashorn.internal.runtime.CodeStore.newCodeStore;
 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
 import static jdk.nashorn.internal.runtime.Source.sourceFor;
@@ -200,14 +201,14 @@
                                 final Map<String,byte[]> classBytes, final Map<Integer, FunctionInitializer> initializers,
                                 final Object[] constants, final int compilationId) {
             if (context.codeStore != null) {
-                context.codeStore.storeScript(cacheKey, source, mainClassName, classBytes, initializers, constants, compilationId);
+                context.codeStore.store(cacheKey, source, mainClassName, classBytes, initializers, constants, compilationId);
             }
         }
 
         @Override
         public StoredScript loadScript(final Source source, final String functionKey) {
             if (context.codeStore != null) {
-                return context.codeStore.loadScript(source, functionKey);
+                return context.codeStore.load(source, functionKey);
             }
             return null;
         }
@@ -463,8 +464,7 @@
 
         if (env._persistent_cache) {
             try {
-                final String cacheDir = Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache");
-                codeStore = new CodeStore(this, cacheDir);
+                codeStore = newCodeStore(this);
             } catch (final IOException e) {
                 throw new RuntimeException("Error initializing code cache", e);
             }
@@ -1117,7 +1117,7 @@
         final String cacheKey = useCodeStore ? CodeStore.getCacheKey(0, null) : null;
 
         if (useCodeStore) {
-            storedScript = codeStore.loadScript(source, cacheKey);
+            storedScript = codeStore.load(source, cacheKey);
         }
 
         if (storedScript == null) {
@@ -1194,15 +1194,16 @@
     private static Class<?> install(final StoredScript storedScript, final Source source, final CodeInstaller<ScriptEnvironment> installer) {
 
         final Map<String, Class<?>> installedClasses = new HashMap<>();
+        final Map<String, byte[]>   classBytes       = storedScript.getClassBytes();
         final Object[] constants       = storedScript.getConstants();
         final String   mainClassName   = storedScript.getMainClassName();
-        final byte[]   mainClassBytes  = storedScript.getClassBytes().get(mainClassName);
+        final byte[]   mainClassBytes  = classBytes.get(mainClassName);
         final Class<?> mainClass       = installer.install(mainClassName, mainClassBytes);
         final Map<Integer, FunctionInitializer> initialzers = storedScript.getInitializers();
 
         installedClasses.put(mainClassName, mainClass);
 
-        for (final Map.Entry<String, byte[]> entry : storedScript.getClassBytes().entrySet()) {
+        for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
             final String className = entry.getKey();
             if (className.equals(mainClassName)) {
                 continue;
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/FunctionInitializer.java	Wed Jul 05 20:01:50 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/FunctionInitializer.java	Fri Sep 19 13:13:20 2014 +0200
@@ -60,6 +60,17 @@
     }
 
     /**
+     * Copy constructor.
+     *
+     * @param init original initializer
+     */
+    FunctionInitializer(final FunctionInitializer init) {
+        this.className = init.getClassName();
+        this.methodType = init.getMethodType();
+        this.flags = init.getFlags();
+    }
+
+    /**
      * Constructor.
      *
      * @param functionNode the function node
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Wed Jul 05 20:01:50 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Fri Sep 19 13:13:20 2014 +0200
@@ -491,14 +491,15 @@
     private FunctionInitializer install(final StoredScript script) {
 
         final Map<String, Class<?>> installedClasses = new HashMap<>();
+        final Map<String, byte[]>   classBytes       = script.getClassBytes();
         final String   mainClassName   = script.getMainClassName();
-        final byte[]   mainClassBytes  = script.getClassBytes().get(mainClassName);
+        final byte[]   mainClassBytes  = classBytes.get(mainClassName);
 
         final Class<?> mainClass       = installer.install(mainClassName, mainClassBytes);
 
         installedClasses.put(mainClassName, mainClass);
 
-        for (final Map.Entry<String, byte[]> entry : script.getClassBytes().entrySet()) {
+        for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
             final String className = entry.getKey();
             final byte[] code = entry.getValue();
 
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/StoredScript.java	Wed Jul 05 20:01:50 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/StoredScript.java	Fri Sep 19 13:13:20 2014 +0200
@@ -27,6 +27,7 @@
 
 import java.io.Serializable;
 import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
@@ -83,7 +84,11 @@
      * @return map of class bytes
      */
     public Map<String, byte[]> getClassBytes() {
-        return classBytes;
+        final Map<String, byte[]> clonedMap = new LinkedHashMap<>();
+        for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
+            clonedMap.put(entry.getKey(), entry.getValue().clone());
+        }
+        return clonedMap;
     }
 
     /**
@@ -91,11 +96,19 @@
      * @return constants array
      */
     public Object[] getConstants() {
-        return constants;
+        return constants.clone();
     }
 
-    Map<Integer, FunctionInitializer> getInitializers() {
-        return initializers;
+    /**
+     * Returns the function initializers map.
+     * @return The initializers map.
+     */
+    public Map<Integer, FunctionInitializer> getInitializers() {
+        final Map<Integer, FunctionInitializer> clonedMap = new LinkedHashMap<>();
+        for (final Map.Entry<Integer, FunctionInitializer> entry : initializers.entrySet()) {
+            clonedMap.put(entry.getKey(), new FunctionInitializer(entry.getValue()));
+        }
+        return clonedMap;
     }
 
     @Override