CodeStore.java revision 1036:f0b5e3900a10
184865Sobrien/* 284865Sobrien * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. 384865Sobrien * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 484865Sobrien * 584865Sobrien * This code is free software; you can redistribute it and/or modify it 684865Sobrien * under the terms of the GNU General Public License version 2 only, as 784865Sobrien * published by the Free Software Foundation. Oracle designates this 884865Sobrien * particular file as subject to the "Classpath" exception as provided 984865Sobrien * by Oracle in the LICENSE file that accompanied this code. 1084865Sobrien * 1184865Sobrien * This code is distributed in the hope that it will be useful, but WITHOUT 1284865Sobrien * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1384865Sobrien * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1484865Sobrien * version 2 for more details (a copy is included in the LICENSE file that 1584865Sobrien * accompanied this code). 1684865Sobrien * 1784865Sobrien * You should have received a copy of the GNU General Public License version 1884865Sobrien * 2 along with this work; if not, write to the Free Software Foundation, 19218822Sdim * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20218822Sdim * 2184865Sobrien * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2284865Sobrien * or visit www.oracle.com if you need additional information or have any 2384865Sobrien * questions. 2484865Sobrien */ 2584865Sobrien 2684865Sobrienpackage jdk.nashorn.internal.runtime; 2784865Sobrien 2884865Sobrienimport java.io.BufferedInputStream; 2984865Sobrienimport java.io.BufferedOutputStream; 3084865Sobrienimport java.io.File; 3184865Sobrienimport java.io.FileInputStream; 3284865Sobrienimport java.io.FileOutputStream; 3384865Sobrienimport java.io.IOException; 3484865Sobrienimport java.io.ObjectInputStream; 3584865Sobrienimport java.io.ObjectOutputStream; 3684865Sobrienimport java.io.Serializable; 3784865Sobrienimport java.security.AccessControlException; 3884865Sobrienimport java.security.AccessController; 3984865Sobrienimport java.security.PrivilegedActionException; 4084865Sobrienimport java.security.PrivilegedExceptionAction; 4184865Sobrienimport java.util.Iterator; 4284865Sobrienimport java.util.Map; 4384865Sobrienimport java.util.ServiceLoader; 4484865Sobrienimport jdk.nashorn.internal.codegen.types.Type; 4584865Sobrienimport jdk.nashorn.internal.runtime.logging.DebugLogger; 4684865Sobrienimport jdk.nashorn.internal.runtime.logging.Loggable; 4784865Sobrienimport jdk.nashorn.internal.runtime.logging.Logger; 4884865Sobrienimport jdk.nashorn.internal.runtime.options.Options; 4984865Sobrien 5084865Sobrien/** 5184865Sobrien * A code cache for persistent caching of compiled scripts. 5284865Sobrien */ 5384865Sobrien@Logger(name="codestore") 5484865Sobrienpublic abstract class CodeStore implements Loggable { 5584865Sobrien 5684865Sobrien /** 5784865Sobrien * Permission needed to provide a CodeStore instance via ServiceLoader. 5884865Sobrien */ 5984865Sobrien public final static String NASHORN_PROVIDE_CODE_STORE = "nashorn.provideCodeStore"; 6084865Sobrien 6184865Sobrien private DebugLogger log; 6284865Sobrien 6384865Sobrien /** 6484865Sobrien * Constructor 6584865Sobrien */ 6684865Sobrien protected CodeStore() { 6784865Sobrien } 6884865Sobrien 6984865Sobrien @Override 7084865Sobrien public DebugLogger initLogger(final Context context) { 7184865Sobrien log = context.getLogger(getClass()); 7284865Sobrien return log; 7384865Sobrien } 7484865Sobrien 7584865Sobrien @Override 7684865Sobrien public DebugLogger getLogger() { 7784865Sobrien return log; 7884865Sobrien } 7984865Sobrien 8084865Sobrien /** 8184865Sobrien * Returns a new code store instance. 8284865Sobrien * 8384865Sobrien * @param context the current context 8484865Sobrien * @return The instance 8584865Sobrien * @throws IOException If an error occurs 8684865Sobrien */ 8784865Sobrien public static CodeStore newCodeStore(final Context context) throws IOException { 8884865Sobrien final Class<CodeStore> baseClass = CodeStore.class; 8984865Sobrien try { 9084865Sobrien // security check first 9184865Sobrien final SecurityManager sm = System.getSecurityManager(); 9284865Sobrien if (sm != null) { 9384865Sobrien sm.checkPermission(new RuntimePermission(NASHORN_PROVIDE_CODE_STORE)); 9484865Sobrien } 9584865Sobrien final ServiceLoader<CodeStore> services = ServiceLoader.load(baseClass); 9684865Sobrien final Iterator<CodeStore> iterator = services.iterator(); 9784865Sobrien if (iterator.hasNext()) { 9884865Sobrien final CodeStore store = iterator.next(); 9984865Sobrien store.initLogger(context).info("using code store provider ", store.getClass().getCanonicalName()); 10084865Sobrien return store; 10184865Sobrien } 10284865Sobrien } catch (final AccessControlException e) { 10384865Sobrien context.getLogger(CodeStore.class).warning("failed to load code store provider ", e); 10484865Sobrien } 10584865Sobrien final CodeStore store = new DirectoryCodeStore(); 10684865Sobrien store.initLogger(context); 10784865Sobrien return store; 10884865Sobrien } 10984865Sobrien 11084865Sobrien 11184865Sobrien /** 11284865Sobrien * Store a compiled script in the cache. 11384865Sobrien * 11484865Sobrien * @param functionKey the function key 11584865Sobrien * @param source the source 11684865Sobrien * @param mainClassName the main class name 11784865Sobrien * @param classBytes a map of class bytes 11884865Sobrien * @param initializers the function initializers 11984865Sobrien * @param constants the constants array 12084865Sobrien * @param compilationId the compilation id 12184865Sobrien * 12284865Sobrien * @return stored script 12384865Sobrien */ 12484865Sobrien public StoredScript store(final String functionKey, 12584865Sobrien final Source source, 12684865Sobrien final String mainClassName, 12784865Sobrien final Map<String, byte[]> classBytes, 12884865Sobrien final Map<Integer, FunctionInitializer> initializers, 12984865Sobrien final Object[] constants, 13084865Sobrien final int compilationId) { 13184865Sobrien return store(functionKey, source, storedScriptFor(source, mainClassName, classBytes, initializers, constants, compilationId)); 13284865Sobrien } 13384865Sobrien 13484865Sobrien /** 13584865Sobrien * Stores a compiled script. 13684865Sobrien * 13784865Sobrien * @param functionKey the function key 13884865Sobrien * @param source the source 13984865Sobrien * @param script The compiled script 14084865Sobrien * @return The compiled script or {@code null} if not stored 14184865Sobrien */ 14284865Sobrien public abstract StoredScript store(final String functionKey, 14384865Sobrien final Source source, 14484865Sobrien final StoredScript script); 14584865Sobrien 14684865Sobrien /** 14784865Sobrien * Return a compiled script from the cache, or null if it isn't found. 14884865Sobrien * 149 * @param source the source 150 * @param functionKey the function key 151 * @return the stored script or null 152 */ 153 public abstract StoredScript load(final Source source, final String functionKey); 154 155 /** 156 * Returns a new StoredScript instance. 157 * 158 * @param source the source 159 * @param mainClassName the main class name 160 * @param classBytes a map of class bytes 161 * @param initializers function initializers 162 * @param constants the constants array 163 * @param compilationId the compilation id 164 * 165 * @return The compiled script 166 */ 167 public StoredScript storedScriptFor(final Source source, final String mainClassName, 168 final Map<String, byte[]> classBytes, 169 final Map<Integer, FunctionInitializer> initializers, 170 final Object[] constants, final int compilationId) { 171 for (final Object constant : constants) { 172 // Make sure all constant data is serializable 173 if (!(constant instanceof Serializable)) { 174 getLogger().warning("cannot store ", source, " non serializable constant ", constant); 175 return null; 176 } 177 } 178 return new StoredScript(compilationId, mainClassName, classBytes, initializers, constants); 179 } 180 181 /** 182 * Generate a string representing the function with {@code functionId} and {@code paramTypes}. 183 * @param functionId function id 184 * @param paramTypes parameter types 185 * @return a string representing the function 186 */ 187 public static String getCacheKey(final int functionId, final Type[] paramTypes) { 188 final StringBuilder b = new StringBuilder().append(functionId); 189 if(paramTypes != null && paramTypes.length > 0) { 190 b.append('-'); 191 for(final Type t: paramTypes) { 192 b.append(Type.getShortSignatureDescriptor(t)); 193 } 194 } 195 return b.toString(); 196 } 197 198 /** 199 * A store using a file system directory. 200 */ 201 public static class DirectoryCodeStore extends CodeStore { 202 203 // Default minimum size for storing a compiled script class 204 private final static int DEFAULT_MIN_SIZE = 1000; 205 206 private final File dir; 207 private final boolean readOnly; 208 private final int minSize; 209 210 /** 211 * Constructor 212 * 213 * @throws IOException 214 */ 215 public DirectoryCodeStore() throws IOException { 216 this(Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE); 217 } 218 219 /** 220 * Constructor 221 * 222 * @param path directory to store code in 223 * @param readOnly is this a read only code store 224 * @param minSize minimum file size for caching scripts 225 * @throws IOException 226 */ 227 public DirectoryCodeStore(final String path, final boolean readOnly, final int minSize) throws IOException { 228 this.dir = checkDirectory(path, readOnly); 229 this.readOnly = readOnly; 230 this.minSize = minSize; 231 } 232 233 private static File checkDirectory(final String path, final boolean readOnly) throws IOException { 234 try { 235 return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() { 236 @Override 237 public File run() throws IOException { 238 final File dir = new File(path).getAbsoluteFile(); 239 if (readOnly) { 240 if (!dir.exists() || !dir.isDirectory()) { 241 throw new IOException("Not a directory: " + dir.getPath()); 242 } else if (!dir.canRead()) { 243 throw new IOException("Directory not readable: " + dir.getPath()); 244 } 245 } else if (!dir.exists() && !dir.mkdirs()) { 246 throw new IOException("Could not create directory: " + dir.getPath()); 247 } else if (!dir.isDirectory()) { 248 throw new IOException("Not a directory: " + dir.getPath()); 249 } else if (!dir.canRead() || !dir.canWrite()) { 250 throw new IOException("Directory not readable or writable: " + dir.getPath()); 251 } 252 return dir; 253 } 254 }); 255 } catch (final PrivilegedActionException e) { 256 throw (IOException) e.getException(); 257 } 258 } 259 260 @Override 261 public StoredScript load(final Source source, final String functionKey) { 262 if (source.getLength() < minSize) { 263 return null; 264 } 265 266 final File file = getCacheFile(source, functionKey); 267 268 try { 269 return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() { 270 @Override 271 public StoredScript run() throws IOException, ClassNotFoundException { 272 if (!file.exists()) { 273 return null; 274 } 275 try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) { 276 final StoredScript storedScript = (StoredScript) in.readObject(); 277 getLogger().info("loaded ", source, "-", functionKey); 278 return storedScript; 279 } 280 } 281 }); 282 } catch (final PrivilegedActionException e) { 283 getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException()); 284 return null; 285 } 286 } 287 288 @Override 289 public StoredScript store(final String functionKey, final Source source, final StoredScript script) { 290 if (readOnly || script == null || belowThreshold(source)) { 291 return null; 292 } 293 294 final File file = getCacheFile(source, functionKey); 295 296 try { 297 return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() { 298 @Override 299 public StoredScript run() throws IOException { 300 try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { 301 out.writeObject(script); 302 } 303 getLogger().info("stored ", source, "-", functionKey); 304 return script; 305 } 306 }); 307 } catch (final PrivilegedActionException e) { 308 getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException()); 309 return null; 310 } 311 } 312 313 314 private File getCacheFile(final Source source, final String functionKey) { 315 return new File(dir, source.getDigest() + '-' + functionKey); 316 } 317 318 private boolean belowThreshold(final Source source) { 319 if (source.getLength() < minSize) { 320 getLogger().info("below size threshold ", source); 321 return true; 322 } 323 return false; 324 } 325 } 326} 327 328