Fri, 19 Sep 2014 13:13:20 +0200
8046202: Make persistent code store more flexible
Reviewed-by: lagergren, sundar
1.1 --- a/src/jdk/nashorn/internal/runtime/CodeStore.java Wed Sep 17 15:02:42 2014 +0530 1.2 +++ b/src/jdk/nashorn/internal/runtime/CodeStore.java Fri Sep 19 13:13:20 2014 +0200 1.3 @@ -34,51 +34,42 @@ 1.4 import java.io.ObjectInputStream; 1.5 import java.io.ObjectOutputStream; 1.6 import java.io.Serializable; 1.7 +import java.security.AccessControlException; 1.8 import java.security.AccessController; 1.9 import java.security.PrivilegedActionException; 1.10 import java.security.PrivilegedExceptionAction; 1.11 +import java.util.Iterator; 1.12 import java.util.Map; 1.13 +import java.util.ServiceLoader; 1.14 import jdk.nashorn.internal.codegen.types.Type; 1.15 import jdk.nashorn.internal.runtime.logging.DebugLogger; 1.16 import jdk.nashorn.internal.runtime.logging.Loggable; 1.17 import jdk.nashorn.internal.runtime.logging.Logger; 1.18 +import jdk.nashorn.internal.runtime.options.Options; 1.19 1.20 /** 1.21 * A code cache for persistent caching of compiled scripts. 1.22 */ 1.23 @Logger(name="codestore") 1.24 -final class CodeStore implements Loggable { 1.25 +public abstract class CodeStore implements Loggable { 1.26 1.27 - private final File dir; 1.28 - private final int minSize; 1.29 - private final DebugLogger log; 1.30 + /** 1.31 + * Permission needed to provide a CodeStore instance via ServiceLoader. 1.32 + */ 1.33 + public final static String NASHORN_PROVIDE_CODE_STORE = "nashorn.provideCodeStore"; 1.34 1.35 - // Default minimum size for storing a compiled script class 1.36 - private final static int DEFAULT_MIN_SIZE = 1000; 1.37 + private DebugLogger log; 1.38 1.39 /** 1.40 * Constructor 1.41 - * @throws IOException 1.42 */ 1.43 - public CodeStore(final Context context, final String path) throws IOException { 1.44 - this(context, path, DEFAULT_MIN_SIZE); 1.45 - } 1.46 - 1.47 - /** 1.48 - * Constructor 1.49 - * @param path directory to store code in 1.50 - * @param minSize minimum file size for caching scripts 1.51 - * @throws IOException 1.52 - */ 1.53 - public CodeStore(final Context context, final String path, final int minSize) throws IOException { 1.54 - this.dir = checkDirectory(path); 1.55 - this.minSize = minSize; 1.56 - this.log = initLogger(context); 1.57 + protected CodeStore() { 1.58 } 1.59 1.60 @Override 1.61 public DebugLogger initLogger(final Context context) { 1.62 - return context.getLogger(getClass()); 1.63 + log = context.getLogger(getClass()); 1.64 + return log; 1.65 } 1.66 1.67 @Override 1.68 @@ -86,29 +77,101 @@ 1.69 return log; 1.70 } 1.71 1.72 - private static File checkDirectory(final String path) throws IOException { 1.73 + /** 1.74 + * Returns a new code store instance. 1.75 + * 1.76 + * @param context the current context 1.77 + * @return The instance 1.78 + * @throws IOException If an error occurs 1.79 + */ 1.80 + public static CodeStore newCodeStore(final Context context) throws IOException { 1.81 + final Class<CodeStore> baseClass = CodeStore.class; 1.82 try { 1.83 - return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() { 1.84 - @Override 1.85 - public File run() throws IOException { 1.86 - final File dir = new File(path).getAbsoluteFile(); 1.87 - if (!dir.exists() && !dir.mkdirs()) { 1.88 - throw new IOException("Could not create directory: " + dir.getPath()); 1.89 - } else if (!dir.isDirectory()) { 1.90 - throw new IOException("Not a directory: " + dir.getPath()); 1.91 - } else if (!dir.canRead() || !dir.canWrite()) { 1.92 - throw new IOException("Directory not readable or writable: " + dir.getPath()); 1.93 - } 1.94 - return dir; 1.95 - } 1.96 - }); 1.97 - } catch (final PrivilegedActionException e) { 1.98 - throw (IOException) e.getException(); 1.99 + // security check first 1.100 + final SecurityManager sm = System.getSecurityManager(); 1.101 + if (sm != null) { 1.102 + sm.checkPermission(new RuntimePermission(NASHORN_PROVIDE_CODE_STORE)); 1.103 + } 1.104 + final ServiceLoader<CodeStore> services = ServiceLoader.load(baseClass); 1.105 + final Iterator<CodeStore> iterator = services.iterator(); 1.106 + if (iterator.hasNext()) { 1.107 + final CodeStore store = iterator.next(); 1.108 + store.initLogger(context).info("using code store provider ", store.getClass().getCanonicalName()); 1.109 + return store; 1.110 + } 1.111 + } catch (final AccessControlException e) { 1.112 + context.getLogger(CodeStore.class).warning("failed to load code store provider ", e); 1.113 } 1.114 + final CodeStore store = new DirectoryCodeStore(); 1.115 + store.initLogger(context); 1.116 + return store; 1.117 } 1.118 1.119 - private File getCacheFile(final Source source, final String functionKey) { 1.120 - return new File(dir, source.getDigest() + '-' + functionKey); 1.121 + 1.122 + /** 1.123 + * Store a compiled script in the cache. 1.124 + * 1.125 + * @param functionKey the function key 1.126 + * @param source the source 1.127 + * @param mainClassName the main class name 1.128 + * @param classBytes a map of class bytes 1.129 + * @param initializers the function initializers 1.130 + * @param constants the constants array 1.131 + * @param compilationId the compilation id 1.132 + */ 1.133 + public StoredScript store(final String functionKey, 1.134 + final Source source, 1.135 + final String mainClassName, 1.136 + final Map<String, byte[]> classBytes, 1.137 + final Map<Integer, FunctionInitializer> initializers, 1.138 + final Object[] constants, 1.139 + final int compilationId) { 1.140 + return store(functionKey, source, storedScriptFor(source, mainClassName, classBytes, initializers, constants, compilationId)); 1.141 + } 1.142 + 1.143 + /** 1.144 + * Stores a compiled script. 1.145 + * 1.146 + * @param functionKey the function key 1.147 + * @param source the source 1.148 + * @param script The compiled script 1.149 + * @return The compiled script or {@code null} if not stored 1.150 + */ 1.151 + public abstract StoredScript store(final String functionKey, 1.152 + final Source source, 1.153 + final StoredScript script); 1.154 + 1.155 + /** 1.156 + * Return a compiled script from the cache, or null if it isn't found. 1.157 + * 1.158 + * @param source the source 1.159 + * @param functionKey the function key 1.160 + * @return the stored script or null 1.161 + */ 1.162 + public abstract StoredScript load(final Source source, final String functionKey); 1.163 + 1.164 + /** 1.165 + * Returns a new StoredScript instance. 1.166 + * 1.167 + * @param mainClassName the main class name 1.168 + * @param classBytes a map of class bytes 1.169 + * @param initializers function initializers 1.170 + * @param constants the constants array 1.171 + * @param compilationId the compilation id 1.172 + * @return The compiled script 1.173 + */ 1.174 + public StoredScript storedScriptFor(final Source source, final String mainClassName, 1.175 + final Map<String, byte[]> classBytes, 1.176 + final Map<Integer, FunctionInitializer> initializers, 1.177 + final Object[] constants, final int compilationId) { 1.178 + for (final Object constant : constants) { 1.179 + // Make sure all constant data is serializable 1.180 + if (!(constant instanceof Serializable)) { 1.181 + getLogger().warning("cannot store ", source, " non serializable constant ", constant); 1.182 + return null; 1.183 + } 1.184 + } 1.185 + return new StoredScript(compilationId, mainClassName, classBytes, initializers, constants); 1.186 } 1.187 1.188 /** 1.189 @@ -129,77 +192,130 @@ 1.190 } 1.191 1.192 /** 1.193 - * Return a compiled script from the cache, or null if it isn't found. 1.194 - * 1.195 - * @param source the source 1.196 - * @param functionKey the function key 1.197 - * @return the stored script or null 1.198 + * A store using a file system directory. 1.199 */ 1.200 - public StoredScript loadScript(final Source source, final String functionKey) { 1.201 - if (source.getLength() < minSize) { 1.202 - return null; 1.203 + public static class DirectoryCodeStore extends CodeStore { 1.204 + 1.205 + // Default minimum size for storing a compiled script class 1.206 + private final static int DEFAULT_MIN_SIZE = 1000; 1.207 + 1.208 + private final File dir; 1.209 + private final boolean readOnly; 1.210 + private final int minSize; 1.211 + 1.212 + /** 1.213 + * Constructor 1.214 + * 1.215 + * @throws IOException 1.216 + */ 1.217 + public DirectoryCodeStore() throws IOException { 1.218 + this(Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE); 1.219 } 1.220 1.221 - final File file = getCacheFile(source, functionKey); 1.222 + /** 1.223 + * Constructor 1.224 + * 1.225 + * @param path directory to store code in 1.226 + * @param minSize minimum file size for caching scripts 1.227 + * @throws IOException 1.228 + */ 1.229 + public DirectoryCodeStore(final String path, final boolean readOnly, final int minSize) throws IOException { 1.230 + this.dir = checkDirectory(path, readOnly); 1.231 + this.readOnly = readOnly; 1.232 + this.minSize = minSize; 1.233 + } 1.234 1.235 - try { 1.236 - return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() { 1.237 - @Override 1.238 - public StoredScript run() throws IOException, ClassNotFoundException { 1.239 - if (!file.exists()) { 1.240 - return null; 1.241 + private static File checkDirectory(final String path, final boolean readOnly) throws IOException { 1.242 + try { 1.243 + return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() { 1.244 + @Override 1.245 + public File run() throws IOException { 1.246 + final File dir = new File(path).getAbsoluteFile(); 1.247 + if (readOnly) { 1.248 + if (!dir.exists() || !dir.isDirectory()) { 1.249 + throw new IOException("Not a directory: " + dir.getPath()); 1.250 + } else if (!dir.canRead()) { 1.251 + throw new IOException("Directory not readable: " + dir.getPath()); 1.252 + } 1.253 + } else if (!dir.exists() && !dir.mkdirs()) { 1.254 + throw new IOException("Could not create directory: " + dir.getPath()); 1.255 + } else if (!dir.isDirectory()) { 1.256 + throw new IOException("Not a directory: " + dir.getPath()); 1.257 + } else if (!dir.canRead() || !dir.canWrite()) { 1.258 + throw new IOException("Directory not readable or writable: " + dir.getPath()); 1.259 + } 1.260 + return dir; 1.261 } 1.262 - try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) { 1.263 - final StoredScript storedScript = (StoredScript) in.readObject(); 1.264 - getLogger().info("loaded ", source, "-", functionKey); 1.265 - return storedScript; 1.266 - } 1.267 - } 1.268 - }); 1.269 - } catch (final PrivilegedActionException e) { 1.270 - getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException()); 1.271 - return null; 1.272 - } 1.273 - } 1.274 - 1.275 - /** 1.276 - * Store a compiled script in the cache. 1.277 - * 1.278 - * @param functionKey the function key 1.279 - * @param source the source 1.280 - * @param mainClassName the main class name 1.281 - * @param classBytes a map of class bytes 1.282 - * @param constants the constants array 1.283 - */ 1.284 - public void storeScript(final String functionKey, final Source source, final String mainClassName, final Map<String, byte[]> classBytes, 1.285 - final Map<Integer, FunctionInitializer> initializers, final Object[] constants, final int compilationId) { 1.286 - if (source.getLength() < minSize) { 1.287 - return; 1.288 - } 1.289 - for (final Object constant : constants) { 1.290 - // Make sure all constant data is serializable 1.291 - if (! (constant instanceof Serializable)) { 1.292 - getLogger().warning("cannot store ", source, " non serializable constant ", constant); 1.293 - return; 1.294 + }); 1.295 + } catch (final PrivilegedActionException e) { 1.296 + throw (IOException) e.getException(); 1.297 } 1.298 } 1.299 1.300 - final File file = getCacheFile(source, functionKey); 1.301 - final StoredScript script = new StoredScript(compilationId, mainClassName, classBytes, initializers, constants); 1.302 + @Override 1.303 + public StoredScript load(final Source source, final String functionKey) { 1.304 + if (source.getLength() < minSize) { 1.305 + return null; 1.306 + } 1.307 1.308 - try { 1.309 - AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { 1.310 - @Override 1.311 - public Void run() throws IOException { 1.312 - try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { 1.313 - out.writeObject(script); 1.314 + final File file = getCacheFile(source, functionKey); 1.315 + 1.316 + try { 1.317 + return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() { 1.318 + @Override 1.319 + public StoredScript run() throws IOException, ClassNotFoundException { 1.320 + if (!file.exists()) { 1.321 + return null; 1.322 + } 1.323 + try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) { 1.324 + final StoredScript storedScript = (StoredScript) in.readObject(); 1.325 + getLogger().info("loaded ", source, "-", functionKey); 1.326 + return storedScript; 1.327 + } 1.328 } 1.329 - getLogger().info("stored ", source, "-", functionKey); 1.330 - return null; 1.331 - } 1.332 - }); 1.333 - } catch (final PrivilegedActionException e) { 1.334 - getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException()); 1.335 + }); 1.336 + } catch (final PrivilegedActionException e) { 1.337 + getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException()); 1.338 + return null; 1.339 + } 1.340 + } 1.341 + 1.342 + @Override 1.343 + public StoredScript store(final String functionKey, final Source source, final StoredScript script) { 1.344 + if (readOnly || script == null || belowThreshold(source)) { 1.345 + return null; 1.346 + } 1.347 + 1.348 + final File file = getCacheFile(source, functionKey); 1.349 + 1.350 + try { 1.351 + return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() { 1.352 + @Override 1.353 + public StoredScript run() throws IOException { 1.354 + try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { 1.355 + out.writeObject(script); 1.356 + } 1.357 + getLogger().info("stored ", source, "-", functionKey); 1.358 + return script; 1.359 + } 1.360 + }); 1.361 + } catch (final PrivilegedActionException e) { 1.362 + getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException()); 1.363 + return null; 1.364 + } 1.365 + } 1.366 + 1.367 + 1.368 + private File getCacheFile(final Source source, final String functionKey) { 1.369 + return new File(dir, source.getDigest() + '-' + functionKey); 1.370 + } 1.371 + 1.372 + private boolean belowThreshold(final Source source) { 1.373 + if (source.getLength() < minSize) { 1.374 + getLogger().info("below size threshold ", source); 1.375 + return true; 1.376 + } 1.377 + return false; 1.378 } 1.379 } 1.380 }
2.1 --- a/src/jdk/nashorn/internal/runtime/Context.java Wed Sep 17 15:02:42 2014 +0530 2.2 +++ b/src/jdk/nashorn/internal/runtime/Context.java Fri Sep 19 13:13:20 2014 +0200 2.3 @@ -29,6 +29,7 @@ 2.4 import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION; 2.5 import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; 2.6 import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE; 2.7 +import static jdk.nashorn.internal.runtime.CodeStore.newCodeStore; 2.8 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 2.9 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 2.10 import static jdk.nashorn.internal.runtime.Source.sourceFor; 2.11 @@ -200,14 +201,14 @@ 2.12 final Map<String,byte[]> classBytes, final Map<Integer, FunctionInitializer> initializers, 2.13 final Object[] constants, final int compilationId) { 2.14 if (context.codeStore != null) { 2.15 - context.codeStore.storeScript(cacheKey, source, mainClassName, classBytes, initializers, constants, compilationId); 2.16 + context.codeStore.store(cacheKey, source, mainClassName, classBytes, initializers, constants, compilationId); 2.17 } 2.18 } 2.19 2.20 @Override 2.21 public StoredScript loadScript(final Source source, final String functionKey) { 2.22 if (context.codeStore != null) { 2.23 - return context.codeStore.loadScript(source, functionKey); 2.24 + return context.codeStore.load(source, functionKey); 2.25 } 2.26 return null; 2.27 } 2.28 @@ -463,8 +464,7 @@ 2.29 2.30 if (env._persistent_cache) { 2.31 try { 2.32 - final String cacheDir = Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"); 2.33 - codeStore = new CodeStore(this, cacheDir); 2.34 + codeStore = newCodeStore(this); 2.35 } catch (final IOException e) { 2.36 throw new RuntimeException("Error initializing code cache", e); 2.37 } 2.38 @@ -1117,7 +1117,7 @@ 2.39 final String cacheKey = useCodeStore ? CodeStore.getCacheKey(0, null) : null; 2.40 2.41 if (useCodeStore) { 2.42 - storedScript = codeStore.loadScript(source, cacheKey); 2.43 + storedScript = codeStore.load(source, cacheKey); 2.44 } 2.45 2.46 if (storedScript == null) { 2.47 @@ -1194,15 +1194,16 @@ 2.48 private static Class<?> install(final StoredScript storedScript, final Source source, final CodeInstaller<ScriptEnvironment> installer) { 2.49 2.50 final Map<String, Class<?>> installedClasses = new HashMap<>(); 2.51 + final Map<String, byte[]> classBytes = storedScript.getClassBytes(); 2.52 final Object[] constants = storedScript.getConstants(); 2.53 final String mainClassName = storedScript.getMainClassName(); 2.54 - final byte[] mainClassBytes = storedScript.getClassBytes().get(mainClassName); 2.55 + final byte[] mainClassBytes = classBytes.get(mainClassName); 2.56 final Class<?> mainClass = installer.install(mainClassName, mainClassBytes); 2.57 final Map<Integer, FunctionInitializer> initialzers = storedScript.getInitializers(); 2.58 2.59 installedClasses.put(mainClassName, mainClass); 2.60 2.61 - for (final Map.Entry<String, byte[]> entry : storedScript.getClassBytes().entrySet()) { 2.62 + for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) { 2.63 final String className = entry.getKey(); 2.64 if (className.equals(mainClassName)) { 2.65 continue;
3.1 --- a/src/jdk/nashorn/internal/runtime/FunctionInitializer.java Wed Sep 17 15:02:42 2014 +0530 3.2 +++ b/src/jdk/nashorn/internal/runtime/FunctionInitializer.java Fri Sep 19 13:13:20 2014 +0200 3.3 @@ -60,6 +60,17 @@ 3.4 } 3.5 3.6 /** 3.7 + * Copy constructor. 3.8 + * 3.9 + * @param init original initializer 3.10 + */ 3.11 + FunctionInitializer(final FunctionInitializer init) { 3.12 + this.className = init.getClassName(); 3.13 + this.methodType = init.getMethodType(); 3.14 + this.flags = init.getFlags(); 3.15 + } 3.16 + 3.17 + /** 3.18 * Constructor. 3.19 * 3.20 * @param functionNode the function node
4.1 --- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Sep 17 15:02:42 2014 +0530 4.2 +++ b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Fri Sep 19 13:13:20 2014 +0200 4.3 @@ -491,14 +491,15 @@ 4.4 private FunctionInitializer install(final StoredScript script) { 4.5 4.6 final Map<String, Class<?>> installedClasses = new HashMap<>(); 4.7 + final Map<String, byte[]> classBytes = script.getClassBytes(); 4.8 final String mainClassName = script.getMainClassName(); 4.9 - final byte[] mainClassBytes = script.getClassBytes().get(mainClassName); 4.10 + final byte[] mainClassBytes = classBytes.get(mainClassName); 4.11 4.12 final Class<?> mainClass = installer.install(mainClassName, mainClassBytes); 4.13 4.14 installedClasses.put(mainClassName, mainClass); 4.15 4.16 - for (final Map.Entry<String, byte[]> entry : script.getClassBytes().entrySet()) { 4.17 + for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) { 4.18 final String className = entry.getKey(); 4.19 final byte[] code = entry.getValue(); 4.20
5.1 --- a/src/jdk/nashorn/internal/runtime/StoredScript.java Wed Sep 17 15:02:42 2014 +0530 5.2 +++ b/src/jdk/nashorn/internal/runtime/StoredScript.java Fri Sep 19 13:13:20 2014 +0200 5.3 @@ -27,6 +27,7 @@ 5.4 5.5 import java.io.Serializable; 5.6 import java.util.Arrays; 5.7 +import java.util.LinkedHashMap; 5.8 import java.util.Map; 5.9 5.10 /** 5.11 @@ -83,7 +84,11 @@ 5.12 * @return map of class bytes 5.13 */ 5.14 public Map<String, byte[]> getClassBytes() { 5.15 - return classBytes; 5.16 + final Map<String, byte[]> clonedMap = new LinkedHashMap<>(); 5.17 + for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) { 5.18 + clonedMap.put(entry.getKey(), entry.getValue().clone()); 5.19 + } 5.20 + return clonedMap; 5.21 } 5.22 5.23 /** 5.24 @@ -91,11 +96,19 @@ 5.25 * @return constants array 5.26 */ 5.27 public Object[] getConstants() { 5.28 - return constants; 5.29 + return constants.clone(); 5.30 } 5.31 5.32 - Map<Integer, FunctionInitializer> getInitializers() { 5.33 - return initializers; 5.34 + /** 5.35 + * Returns the function initializers map. 5.36 + * @return The initializers map. 5.37 + */ 5.38 + public Map<Integer, FunctionInitializer> getInitializers() { 5.39 + final Map<Integer, FunctionInitializer> clonedMap = new LinkedHashMap<>(); 5.40 + for (final Map.Entry<Integer, FunctionInitializer> entry : initializers.entrySet()) { 5.41 + clonedMap.put(entry.getKey(), new FunctionInitializer(entry.getValue())); 5.42 + } 5.43 + return clonedMap; 5.44 } 5.45 5.46 @Override