diff options
Diffstat (limited to 'src/jdk/nashorn/internal/runtime/CodeStore.java')
-rw-r--r-- | src/jdk/nashorn/internal/runtime/CodeStore.java | 356 |
1 files changed, 260 insertions, 96 deletions
diff --git a/src/jdk/nashorn/internal/runtime/CodeStore.java b/src/jdk/nashorn/internal/runtime/CodeStore.java index 8f595999..4e745ff4 100644 --- a/src/jdk/nashorn/internal/runtime/CodeStore.java +++ b/src/jdk/nashorn/internal/runtime/CodeStore.java @@ -34,145 +34,309 @@ import java.io.IOException; 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.Base64; +import java.util.Iterator; import java.util.Map; +import java.util.ServiceLoader; +import jdk.nashorn.internal.codegen.OptimisticTypesPersistence; +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. */ -final class CodeStore { +@Logger(name="codestore") +public abstract class CodeStore implements Loggable { - private final File dir; - private final int minSize; - - // Message digest to file name encoder - private final static Base64.Encoder BASE64 = Base64.getUrlEncoder().withoutPadding(); + /** + * 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 - * @param path directory to store code in - * @throws IOException */ - public CodeStore(final String path) throws IOException { - this(path, DEFAULT_MIN_SIZE); + protected CodeStore() { } - /** - * Constructor - * @param path directory to store code in - * @param minSize minimum file size for caching scripts - * @throws IOException - */ - public CodeStore(final String path, final int minSize) throws IOException { - this.dir = checkDirectory(path); - this.minSize = minSize; + @Override + public DebugLogger initLogger(final Context context) { + log = context.getLogger(getClass()); + return log; } - private static File checkDirectory(final String path) throws IOException { - 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); - } else if (!dir.isDirectory()) { - throw new IOException("Not a directory: " + dir); - } else if (!dir.canRead() || !dir.canWrite()) { - throw new IOException("Directory not readable or writable: " + dir); - } - return dir; - } - }); - } catch (PrivilegedActionException e) { - throw (IOException) e.getException(); - } + @Override + public DebugLogger getLogger() { + return log; } /** - * Return a compiled script from the cache, or null if it isn't found. + * Returns a new code store instance. * - * @param source the source - * @return the compiled script or null - * @throws IOException - * @throws ClassNotFoundException + * @param context the current context + * @return The instance, or null if code store could not be created */ - public CompiledScript getScript(final Source source) throws IOException, ClassNotFoundException { - if (source.getLength() < minSize) { - return null; - } - - final String digest = BASE64.encodeToString(source.getDigest()); - final File file = new File(dir, digest); - + public static CodeStore newCodeStore(final Context context) { + final Class<CodeStore> baseClass = CodeStore.class; try { - return AccessController.doPrivileged(new PrivilegedExceptionAction<CompiledScript>() { - @Override - public CompiledScript run() throws IOException, ClassNotFoundException { - if (!file.exists()) { - return null; - } - try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) { - CompiledScript compiledScript = (CompiledScript) in.readObject(); - compiledScript.setSource(source); - return compiledScript; - } - } - }); - } catch (PrivilegedActionException e) { - final Exception ex = e.getException(); - if (ex instanceof IOException) { - throw (IOException) ex; - } else if (ex instanceof ClassNotFoundException) { - throw (ClassNotFoundException) ex; + // 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; } - throw (new RuntimeException(ex)); + } catch (final AccessControlException e) { + context.getLogger(CodeStore.class).warning("failed to load code store provider ", e); + } + try { + final CodeStore store = new DirectoryCodeStore(context); + store.initLogger(context); + return store; + } catch (final IOException e) { + context.getLogger(CodeStore.class).warning("failed to create cache directory ", e); + 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 initializers the function initializers + * @param constants the constants array + * @param compilationId the compilation id + * + * @return stored script + */ + 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 source the source * @param mainClassName the main class name * @param classBytes a map of class bytes + * @param initializers function initializers * @param constants the constants array - * @throws IOException + * @param compilationId the compilation id + * + * @return The compiled script */ - public void putScript(final Source source, final String mainClassName, final Map<String, byte[]> classBytes, final Object[] constants) - throws IOException { - if (source.getLength() < minSize) { - return; - } + 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)) { - return; + if (!(constant instanceof Serializable)) { + getLogger().warning("cannot store ", source, " non serializable constant ", constant); + return null; } } + return new StoredScript(compilationId, mainClassName, classBytes, initializers, constants); + } + + /** + * Generate a string representing the function with {@code functionId} and {@code paramTypes}. + * @param functionId function id + * @param paramTypes parameter types + * @return a string representing the function + */ + public static String getCacheKey(final int functionId, final Type[] paramTypes) { + final StringBuilder b = new StringBuilder().append(functionId); + if(paramTypes != null && paramTypes.length > 0) { + b.append('-'); + for(final Type t: paramTypes) { + b.append(Type.getShortSignatureDescriptor(t)); + } + } + return b.toString(); + } - final String digest = BASE64.encodeToString(source.getDigest()); - final File file = new File(dir, digest); - final CompiledScript script = new CompiledScript(source, mainClassName, classBytes, constants); + /** + * A store using a file system directory. + */ + public static class DirectoryCodeStore extends CodeStore { - 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); + // 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 + * + * @param context the current context + * @throws IOException if there are read/write problems with the cache and cache directory + */ + public DirectoryCodeStore(final Context context) throws IOException { + this(context, Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE); + } + + /** + * Constructor + * + * @param context the current context + * @param path directory to store code in + * @param readOnly is this a read only code store + * @param minSize minimum file size for caching scripts + * @throws IOException if there are read/write problems with the cache and cache directory + */ + public DirectoryCodeStore(final Context context, final String path, final boolean readOnly, final int minSize) throws IOException { + this.dir = checkDirectory(path, context.getEnv(), readOnly); + this.readOnly = readOnly; + this.minSize = minSize; + } + + private static File checkDirectory(final String path, final ScriptEnvironment env, final boolean readOnly) throws IOException { + try { + return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() { + @Override + public File run() throws IOException { + final File dir = new File(path, getVersionDir(env)).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(); + } + } + + private static String getVersionDir(final ScriptEnvironment env) throws IOException { + try { + final String versionDir = OptimisticTypesPersistence.getVersionDirName(); + return env._optimistic_types ? versionDir + "_opt" : versionDir; + } catch (final Exception e) { + throw new IOException(e); + } + } + + @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; + } + } + + @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; } - return null; - } - }); - } catch (PrivilegedActionException e) { - throw (IOException) 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; } } } |