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