--- 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