8046202: Make persistent code store more flexible

Reviewed-by: lagergren, sundar
This commit is contained in:
Hannes Wallnöfer 2014-09-19 13:13:20 +02:00
parent 1a2793f29e
commit 7cb28afba6
5 changed files with 257 additions and 115 deletions

View File

@ -34,51 +34,42 @@ 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.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 @@ final class CodeStore implements Loggable {
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 @@ final class CodeStore implements Loggable {
}
/**
* 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<StoredScript>() {
@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<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;
}
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<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;
});
} 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<Void>() {
@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<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;
}
}
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<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;
}
});
} 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;
}
}
}

View File

@ -29,6 +29,7 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS;
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 @@ public final class Context {
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 @@ public final class Context {
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 @@ public final class Context {
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 @@ public final class Context {
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;

View File

@ -59,6 +59,17 @@ public final class FunctionInitializer implements Serializable {
this(functionNode, null);
}
/**
* Copy constructor.
*
* @param init original initializer
*/
FunctionInitializer(final FunctionInitializer init) {
this.className = init.getClassName();
this.methodType = init.getMethodType();
this.flags = init.getFlags();
}
/**
* Constructor.
*

View File

@ -491,14 +491,15 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
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();

View File

@ -27,6 +27,7 @@ package jdk.nashorn.internal.runtime;
import java.io.Serializable;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
/**
@ -83,7 +84,11 @@ public final class StoredScript implements Serializable {
* @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 @@ public final class StoredScript implements Serializable {
* @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