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