# HG changeset patch # User hannesw # Date 1411125200 -7200 # Node ID acb17eade64211b3d7bc3f4abde67ae6f5d83224 # Parent e83ceda86582100f2934d5ad2f1e703dd89f39a1 8046202: Make persistent code store more flexible Reviewed-by: lagergren, sundar diff -r e83ceda86582 -r acb17eade642 src/jdk/nashorn/internal/runtime/CodeStore.java --- a/src/jdk/nashorn/internal/runtime/CodeStore.java Wed Sep 17 15:02:42 2014 +0530 +++ b/src/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 baseClass = CodeStore.class; try { - return AccessController.doPrivileged(new PrivilegedExceptionAction() { - @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 services = ServiceLoader.load(baseClass); + final Iterator 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 classBytes, + final Map 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 classBytes, + final Map 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); + /** + * 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; + } - try { - return AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public StoredScript run() throws IOException, ClassNotFoundException { - if (!file.exists()) { - return null; + private static File checkDirectory(final String path, final boolean readOnly) throws IOException { + try { + return AccessController.doPrivileged(new PrivilegedExceptionAction() { + @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; } - 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; - } - } - - /** - * 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 classBytes, - final Map 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; + }); + } 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; + } - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Void run() throws IOException { - try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { - out.writeObject(script); + final File file = getCacheFile(source, functionKey); + + try { + return AccessController.doPrivileged(new PrivilegedExceptionAction() { + @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; + } } - 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 load ", source, "-", functionKey, ": ", e.getException()); + return null; + } + } + + @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() { + @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; + } + }); + } 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; } } } diff -r e83ceda86582 -r acb17eade642 src/jdk/nashorn/internal/runtime/Context.java --- a/src/jdk/nashorn/internal/runtime/Context.java Wed Sep 17 15:02:42 2014 +0530 +++ b/src/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 classBytes, final Map 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 installer) { final Map> installedClasses = new HashMap<>(); + final Map 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 initialzers = storedScript.getInitializers(); installedClasses.put(mainClassName, mainClass); - for (final Map.Entry entry : storedScript.getClassBytes().entrySet()) { + for (final Map.Entry entry : classBytes.entrySet()) { final String className = entry.getKey(); if (className.equals(mainClassName)) { continue; diff -r e83ceda86582 -r acb17eade642 src/jdk/nashorn/internal/runtime/FunctionInitializer.java --- a/src/jdk/nashorn/internal/runtime/FunctionInitializer.java Wed Sep 17 15:02:42 2014 +0530 +++ b/src/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 diff -r e83ceda86582 -r acb17eade642 src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java --- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Sep 17 15:02:42 2014 +0530 +++ b/src/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> installedClasses = new HashMap<>(); + final Map 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 entry : script.getClassBytes().entrySet()) { + for (final Map.Entry entry : classBytes.entrySet()) { final String className = entry.getKey(); final byte[] code = entry.getValue(); diff -r e83ceda86582 -r acb17eade642 src/jdk/nashorn/internal/runtime/StoredScript.java --- a/src/jdk/nashorn/internal/runtime/StoredScript.java Wed Sep 17 15:02:42 2014 +0530 +++ b/src/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 getClassBytes() { - return classBytes; + final Map clonedMap = new LinkedHashMap<>(); + for (final Map.Entry 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 getInitializers() { - return initializers; + /** + * Returns the function initializers map. + * @return The initializers map. + */ + public Map getInitializers() { + final Map clonedMap = new LinkedHashMap<>(); + for (final Map.Entry entry : initializers.entrySet()) { + clonedMap.put(entry.getKey(), new FunctionInitializer(entry.getValue())); + } + return clonedMap; } @Override