aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/runtime/CodeStore.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jdk/nashorn/internal/runtime/CodeStore.java')
-rw-r--r--src/jdk/nashorn/internal/runtime/CodeStore.java356
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;
}
}
}