1 /* 2 * Copyright (c) 2010, 2014, 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 26 package jdk.nashorn.internal.runtime; 27 28 import java.io.BufferedInputStream; 29 import java.io.BufferedOutputStream; 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.FileOutputStream; 33 import java.io.IOException; 34 import java.io.ObjectInputStream; 35 import java.io.ObjectOutputStream; 36 import java.io.Serializable; 37 import java.security.AccessControlException; 38 import java.security.AccessController; 39 import java.security.PrivilegedActionException; 40 import java.security.PrivilegedExceptionAction; 41 import java.util.Iterator; 42 import java.util.Map; 43 import java.util.ServiceLoader; 44 import jdk.nashorn.internal.codegen.OptimisticTypesPersistence; 45 import jdk.nashorn.internal.codegen.types.Type; 46 import jdk.nashorn.internal.runtime.logging.DebugLogger; 47 import jdk.nashorn.internal.runtime.logging.Loggable; 48 import jdk.nashorn.internal.runtime.logging.Logger; 49 import jdk.nashorn.internal.runtime.options.Options; 50 51 /** 52 * A code cache for persistent caching of compiled scripts. 53 */ 54 @Logger(name="codestore") 55 public abstract class CodeStore implements Loggable { 56 57 /** 58 * Permission needed to provide a CodeStore instance via ServiceLoader. 59 */ 60 public final static String NASHORN_PROVIDE_CODE_STORE = "nashorn.provideCodeStore"; 61 62 private DebugLogger log; 63 64 /** 65 * Constructor 66 */ 67 protected CodeStore() { 68 } 69 70 @Override 71 public DebugLogger initLogger(final Context context) { 72 log = context.getLogger(getClass()); 73 return log; 74 } 75 76 @Override 77 public DebugLogger getLogger() { 78 return log; 79 } 80 81 /** 82 * Returns a new code store instance. 83 * 84 * @param context the current context 85 * @return The instance 86 * @throws IOException If an error occurs 87 */ 88 public static CodeStore newCodeStore(final Context context) throws IOException { 89 final Class<CodeStore> baseClass = CodeStore.class; 90 try { 91 // security check first 92 final SecurityManager sm = System.getSecurityManager(); 93 if (sm != null) { 94 sm.checkPermission(new RuntimePermission(NASHORN_PROVIDE_CODE_STORE)); 95 } 96 final ServiceLoader<CodeStore> services = ServiceLoader.load(baseClass); 97 final Iterator<CodeStore> iterator = services.iterator(); 98 if (iterator.hasNext()) { 99 final CodeStore store = iterator.next(); 100 store.initLogger(context).info("using code store provider ", store.getClass().getCanonicalName()); 101 return store; 102 } 103 } catch (final AccessControlException e) { 104 context.getLogger(CodeStore.class).warning("failed to load code store provider ", e); 105 } 106 final CodeStore store = new DirectoryCodeStore(context); 107 store.initLogger(context); 108 return store; 109 } 110 111 112 /** 113 * Store a compiled script in the cache. 114 * 115 * @param functionKey the function key 116 * @param source the source 117 * @param mainClassName the main class name 118 * @param classBytes a map of class bytes 119 * @param initializers the function initializers 120 * @param constants the constants array 121 * @param compilationId the compilation id 122 * 123 * @return stored script 124 */ 125 public StoredScript store(final String functionKey, 126 final Source source, 127 final String mainClassName, 128 final Map<String, byte[]> classBytes, 129 final Map<Integer, FunctionInitializer> initializers, 130 final Object[] constants, 131 final int compilationId) { 132 return store(functionKey, source, storedScriptFor(source, mainClassName, classBytes, initializers, constants, compilationId)); 133 } 134 135 /** 136 * Stores a compiled script. 137 * 138 * @param functionKey the function key 139 * @param source the source 140 * @param script The compiled script 141 * @return The compiled script or {@code null} if not stored 142 */ 143 public abstract StoredScript store(final String functionKey, 144 final Source source, 145 final StoredScript script); 146 147 /** 148 * Return a compiled script from the cache, or null if it isn't found. 149 * 150 * @param source the source 151 * @param functionKey the function key 152 * @return the stored script or null 153 */ 154 public abstract StoredScript load(final Source source, final String functionKey); 155 156 /** 157 * Returns a new StoredScript instance. 158 * 159 * @param source the source 160 * @param mainClassName the main class name 161 * @param classBytes a map of class bytes 162 * @param initializers function initializers 163 * @param constants the constants array 164 * @param compilationId the compilation id 165 * 166 * @return The compiled script 167 */ 168 public StoredScript storedScriptFor(final Source source, final String mainClassName, 169 final Map<String, byte[]> classBytes, 170 final Map<Integer, FunctionInitializer> initializers, 171 final Object[] constants, final int compilationId) { 172 for (final Object constant : constants) { 173 // Make sure all constant data is serializable 174 if (!(constant instanceof Serializable)) { 175 getLogger().warning("cannot store ", source, " non serializable constant ", constant); 176 return null; 177 } 178 } 179 return new StoredScript(compilationId, mainClassName, classBytes, initializers, constants); 180 } 181 182 /** 183 * Generate a string representing the function with {@code functionId} and {@code paramTypes}. 184 * @param functionId function id 185 * @param paramTypes parameter types 186 * @return a string representing the function 187 */ 188 public static String getCacheKey(final int functionId, final Type[] paramTypes) { 189 final StringBuilder b = new StringBuilder().append(functionId); 190 if(paramTypes != null && paramTypes.length > 0) { 191 b.append('-'); 192 for(final Type t: paramTypes) { 193 b.append(Type.getShortSignatureDescriptor(t)); 194 } 195 } 196 return b.toString(); 197 } 198 199 /** 200 * A store using a file system directory. 201 */ 202 public static class DirectoryCodeStore extends CodeStore { 203 204 // Default minimum size for storing a compiled script class 205 private final static int DEFAULT_MIN_SIZE = 1000; 206 207 private final File dir; 208 private final boolean readOnly; 209 private final int minSize; 210 211 /** 212 * Constructor 213 * 214 * @param context the current context 215 * @throws IOException if there are read/write problems with the cache and cache directory 216 */ 217 public DirectoryCodeStore(final Context context) throws IOException { 218 this(context, Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE); 219 } 220 221 /** 222 * Constructor 223 * 224 * @param context the current context 225 * @param path directory to store code in 226 * @param readOnly is this a read only code store 227 * @param minSize minimum file size for caching scripts 228 * @throws IOException if there are read/write problems with the cache and cache directory 229 */ 230 public DirectoryCodeStore(final Context context, final String path, final boolean readOnly, final int minSize) throws IOException { 231 this.dir = checkDirectory(path, context.getEnv(), readOnly); 232 this.readOnly = readOnly; 233 this.minSize = minSize; 234 } 235 236 private static File checkDirectory(final String path, final ScriptEnvironment env, final boolean readOnly) throws IOException { 237 try { 238 return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() { 239 @Override 240 public File run() throws IOException { 241 final File dir = new File(path, getVersionDir(env)).getAbsoluteFile(); 242 if (readOnly) { 243 if (!dir.exists() || !dir.isDirectory()) { 244 throw new IOException("Not a directory: " + dir.getPath()); 245 } else if (!dir.canRead()) { 246 throw new IOException("Directory not readable: " + dir.getPath()); 247 } 248 } else if (!dir.exists() && !dir.mkdirs()) { 249 throw new IOException("Could not create directory: " + dir.getPath()); 250 } else if (!dir.isDirectory()) { 251 throw new IOException("Not a directory: " + dir.getPath()); 252 } else if (!dir.canRead() || !dir.canWrite()) { 253 throw new IOException("Directory not readable or writable: " + dir.getPath()); 254 } 255 return dir; 256 } 257 }); 258 } catch (final PrivilegedActionException e) { 259 throw (IOException) e.getException(); 260 } 261 } 262 263 private static String getVersionDir(final ScriptEnvironment env) throws IOException { 264 try { 265 final String versionDir = OptimisticTypesPersistence.getVersionDirName(); 266 return env._optimistic_types ? versionDir + "_opt" : versionDir; 267 } catch (final Exception e) { 268 throw new IOException(e); 269 } 270 } 271 272 @Override 273 public StoredScript load(final Source source, final String functionKey) { 274 if (source.getLength() < minSize) { 275 return null; 276 } 277 278 final File file = getCacheFile(source, functionKey); 279 280 try { 281 return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() { 282 @Override 283 public StoredScript run() throws IOException, ClassNotFoundException { 284 if (!file.exists()) { 285 return null; 286 } 287 try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) { 288 final StoredScript storedScript = (StoredScript) in.readObject(); 289 getLogger().info("loaded ", source, "-", functionKey); 290 return storedScript; 291 } 292 } 293 }); 294 } catch (final PrivilegedActionException e) { 295 getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException()); 296 return null; 297 } 298 } 299 300 @Override 301 public StoredScript store(final String functionKey, final Source source, final StoredScript script) { 302 if (readOnly || script == null || belowThreshold(source)) { 303 return null; 304 } 305 306 final File file = getCacheFile(source, functionKey); 307 308 try { 309 return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() { 310 @Override 311 public StoredScript run() throws IOException { 312 try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { 313 out.writeObject(script); 314 } 315 getLogger().info("stored ", source, "-", functionKey); 316 return script; 317 } 318 }); 319 } catch (final PrivilegedActionException e) { 320 getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException()); 321 return null; 322 } 323 } 324 325 326 private File getCacheFile(final Source source, final String functionKey) { 327 return new File(dir, source.getDigest() + '-' + functionKey); 328 } 329 330 private boolean belowThreshold(final Source source) { 331 if (source.getLength() < minSize) { 332 getLogger().info("below size threshold ", source); 333 return true; 334 } 335 return false; 336 } 337 } 338 } 339 --- EOF ---