CodeStore.java revision 1635:70f0c3970211
1/* 2 * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package jdk.nashorn.internal.runtime; 27 28import java.io.BufferedInputStream; 29import java.io.BufferedOutputStream; 30import java.io.File; 31import java.io.FileInputStream; 32import java.io.FileOutputStream; 33import java.io.IOException; 34import java.io.ObjectInputStream; 35import java.io.ObjectOutputStream; 36import java.io.Serializable; 37import java.security.AccessController; 38import java.security.PrivilegedActionException; 39import java.security.PrivilegedExceptionAction; 40import java.util.Map; 41import jdk.nashorn.internal.codegen.OptimisticTypesPersistence; 42import jdk.nashorn.internal.codegen.types.Type; 43import jdk.nashorn.internal.runtime.logging.DebugLogger; 44import jdk.nashorn.internal.runtime.logging.Loggable; 45import jdk.nashorn.internal.runtime.logging.Logger; 46import jdk.nashorn.internal.runtime.options.Options; 47 48/** 49 * A code cache for persistent caching of compiled scripts. 50 */ 51@Logger(name="codestore") 52public abstract class CodeStore implements Loggable { 53 54 private DebugLogger log; 55 56 /** 57 * Constructor 58 */ 59 protected CodeStore() { 60 } 61 62 @Override 63 public DebugLogger initLogger(final Context context) { 64 log = context.getLogger(getClass()); 65 return log; 66 } 67 68 @Override 69 public DebugLogger getLogger() { 70 return log; 71 } 72 73 /** 74 * Returns a new code store instance. 75 * 76 * @param context the current context 77 * @return The instance, or null if code store could not be created 78 */ 79 public static CodeStore newCodeStore(final Context context) { 80 try { 81 final CodeStore store = new DirectoryCodeStore(context); 82 store.initLogger(context); 83 return store; 84 } catch (final IOException e) { 85 context.getLogger(CodeStore.class).warning("failed to create cache directory ", e); 86 return null; 87 } 88 } 89 90 91 /** 92 * Store a compiled script in the cache. 93 * 94 * @param functionKey the function key 95 * @param source the source 96 * @param mainClassName the main class name 97 * @param classBytes a map of class bytes 98 * @param initializers the function initializers 99 * @param constants the constants array 100 * @param compilationId the compilation id 101 * 102 * @return stored script 103 */ 104 public StoredScript store(final String functionKey, 105 final Source source, 106 final String mainClassName, 107 final Map<String, byte[]> classBytes, 108 final Map<Integer, FunctionInitializer> initializers, 109 final Object[] constants, 110 final int compilationId) { 111 return store(functionKey, source, storedScriptFor(source, mainClassName, classBytes, initializers, constants, compilationId)); 112 } 113 114 /** 115 * Stores a compiled script. 116 * 117 * @param functionKey the function key 118 * @param source the source 119 * @param script The compiled script 120 * @return The compiled script or {@code null} if not stored 121 */ 122 public abstract StoredScript store(final String functionKey, 123 final Source source, 124 final StoredScript script); 125 126 /** 127 * Return a compiled script from the cache, or null if it isn't found. 128 * 129 * @param source the source 130 * @param functionKey the function key 131 * @return the stored script or null 132 */ 133 public abstract StoredScript load(final Source source, final String functionKey); 134 135 /** 136 * Returns a new StoredScript instance. 137 * 138 * @param source the source 139 * @param mainClassName the main class name 140 * @param classBytes a map of class bytes 141 * @param initializers function initializers 142 * @param constants the constants array 143 * @param compilationId the compilation id 144 * 145 * @return The compiled script 146 */ 147 public StoredScript storedScriptFor(final Source source, final String mainClassName, 148 final Map<String, byte[]> classBytes, 149 final Map<Integer, FunctionInitializer> initializers, 150 final Object[] constants, final int compilationId) { 151 for (final Object constant : constants) { 152 // Make sure all constant data is serializable 153 if (!(constant instanceof Serializable)) { 154 getLogger().warning("cannot store ", source, " non serializable constant ", constant); 155 return null; 156 } 157 } 158 return new StoredScript(compilationId, mainClassName, classBytes, initializers, constants); 159 } 160 161 /** 162 * Generate a string representing the function with {@code functionId} and {@code paramTypes}. 163 * @param functionId function id 164 * @param paramTypes parameter types 165 * @return a string representing the function 166 */ 167 public static String getCacheKey(final Object functionId, final Type[] paramTypes) { 168 final StringBuilder b = new StringBuilder().append(functionId); 169 if(paramTypes != null && paramTypes.length > 0) { 170 b.append('-'); 171 for(final Type t: paramTypes) { 172 b.append(Type.getShortSignatureDescriptor(t)); 173 } 174 } 175 return b.toString(); 176 } 177 178 /** 179 * A store using a file system directory. 180 */ 181 public static class DirectoryCodeStore extends CodeStore { 182 183 // Default minimum size for storing a compiled script class 184 private final static int DEFAULT_MIN_SIZE = 1000; 185 186 private final File dir; 187 private final boolean readOnly; 188 private final int minSize; 189 190 /** 191 * Constructor 192 * 193 * @param context the current context 194 * @throws IOException if there are read/write problems with the cache and cache directory 195 */ 196 public DirectoryCodeStore(final Context context) throws IOException { 197 this(context, Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE); 198 } 199 200 /** 201 * Constructor 202 * 203 * @param context the current context 204 * @param path directory to store code in 205 * @param readOnly is this a read only code store 206 * @param minSize minimum file size for caching scripts 207 * @throws IOException if there are read/write problems with the cache and cache directory 208 */ 209 public DirectoryCodeStore(final Context context, final String path, final boolean readOnly, final int minSize) throws IOException { 210 this.dir = checkDirectory(path, context.getEnv(), readOnly); 211 this.readOnly = readOnly; 212 this.minSize = minSize; 213 } 214 215 private static File checkDirectory(final String path, final ScriptEnvironment env, final boolean readOnly) throws IOException { 216 try { 217 return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() { 218 @Override 219 public File run() throws IOException { 220 final File dir = new File(path, getVersionDir(env)).getAbsoluteFile(); 221 if (readOnly) { 222 if (!dir.exists() || !dir.isDirectory()) { 223 throw new IOException("Not a directory: " + dir.getPath()); 224 } else if (!dir.canRead()) { 225 throw new IOException("Directory not readable: " + dir.getPath()); 226 } 227 } else if (!dir.exists() && !dir.mkdirs()) { 228 throw new IOException("Could not create directory: " + dir.getPath()); 229 } else if (!dir.isDirectory()) { 230 throw new IOException("Not a directory: " + dir.getPath()); 231 } else if (!dir.canRead() || !dir.canWrite()) { 232 throw new IOException("Directory not readable or writable: " + dir.getPath()); 233 } 234 return dir; 235 } 236 }); 237 } catch (final PrivilegedActionException e) { 238 throw (IOException) e.getException(); 239 } 240 } 241 242 private static String getVersionDir(final ScriptEnvironment env) throws IOException { 243 try { 244 final String versionDir = OptimisticTypesPersistence.getVersionDirName(); 245 return env._optimistic_types ? versionDir + "_opt" : versionDir; 246 } catch (final Exception e) { 247 throw new IOException(e); 248 } 249 } 250 251 @Override 252 public StoredScript load(final Source source, final String functionKey) { 253 if (belowThreshold(source)) { 254 return null; 255 } 256 257 final File file = getCacheFile(source, functionKey); 258 259 try { 260 return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() { 261 @Override 262 public StoredScript run() throws IOException, ClassNotFoundException { 263 if (!file.exists()) { 264 return null; 265 } 266 try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) { 267 final StoredScript storedScript = (StoredScript) in.readObject(); 268 getLogger().info("loaded ", source, "-", functionKey); 269 return storedScript; 270 } 271 } 272 }); 273 } catch (final PrivilegedActionException e) { 274 getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException()); 275 return null; 276 } 277 } 278 279 @Override 280 public StoredScript store(final String functionKey, final Source source, final StoredScript script) { 281 if (readOnly || script == null || belowThreshold(source)) { 282 return null; 283 } 284 285 final File file = getCacheFile(source, functionKey); 286 287 try { 288 return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() { 289 @Override 290 public StoredScript run() throws IOException { 291 try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { 292 out.writeObject(script); 293 } 294 getLogger().info("stored ", source, "-", functionKey); 295 return script; 296 } 297 }); 298 } catch (final PrivilegedActionException e) { 299 getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException()); 300 return null; 301 } 302 } 303 304 305 private File getCacheFile(final Source source, final String functionKey) { 306 return new File(dir, source.getDigest() + '-' + functionKey); 307 } 308 309 private boolean belowThreshold(final Source source) { 310 if (source.getLength() < minSize) { 311 getLogger().info("below size threshold ", source); 312 return true; 313 } 314 return false; 315 } 316 } 317} 318 319