mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-18 02:40:41 +00:00
8046202: Make persistent code store more flexible
Reviewed-by: lagergren, sundar
This commit is contained in:
parent
1a2793f29e
commit
7cb28afba6
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user