nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/CodeStore.java
changeset 26764 c777787a937d
parent 26068 5488f52c2788
child 26768 751b0f427090
equal deleted inserted replaced
26668:1b1ec4291abc 26764:c777787a937d
    32 import java.io.FileOutputStream;
    32 import java.io.FileOutputStream;
    33 import java.io.IOException;
    33 import java.io.IOException;
    34 import java.io.ObjectInputStream;
    34 import java.io.ObjectInputStream;
    35 import java.io.ObjectOutputStream;
    35 import java.io.ObjectOutputStream;
    36 import java.io.Serializable;
    36 import java.io.Serializable;
       
    37 import java.security.AccessControlException;
    37 import java.security.AccessController;
    38 import java.security.AccessController;
    38 import java.security.PrivilegedActionException;
    39 import java.security.PrivilegedActionException;
    39 import java.security.PrivilegedExceptionAction;
    40 import java.security.PrivilegedExceptionAction;
       
    41 import java.util.Iterator;
    40 import java.util.Map;
    42 import java.util.Map;
       
    43 import java.util.ServiceLoader;
    41 import jdk.nashorn.internal.codegen.types.Type;
    44 import jdk.nashorn.internal.codegen.types.Type;
    42 import jdk.nashorn.internal.runtime.logging.DebugLogger;
    45 import jdk.nashorn.internal.runtime.logging.DebugLogger;
    43 import jdk.nashorn.internal.runtime.logging.Loggable;
    46 import jdk.nashorn.internal.runtime.logging.Loggable;
    44 import jdk.nashorn.internal.runtime.logging.Logger;
    47 import jdk.nashorn.internal.runtime.logging.Logger;
       
    48 import jdk.nashorn.internal.runtime.options.Options;
    45 
    49 
    46 /**
    50 /**
    47  * A code cache for persistent caching of compiled scripts.
    51  * A code cache for persistent caching of compiled scripts.
    48  */
    52  */
    49 @Logger(name="codestore")
    53 @Logger(name="codestore")
    50 final class CodeStore implements Loggable {
    54 public abstract class CodeStore implements Loggable {
    51 
    55 
    52     private final File dir;
    56     /**
    53     private final int minSize;
    57      * Permission needed to provide a CodeStore instance via ServiceLoader.
    54     private final DebugLogger log;
    58      */
    55 
    59     public final static String NASHORN_PROVIDE_CODE_STORE = "nashorn.provideCodeStore";
    56     // Default minimum size for storing a compiled script class
    60 
    57     private final static int DEFAULT_MIN_SIZE = 1000;
    61     private DebugLogger log;
    58 
    62 
    59     /**
    63     /**
    60      * Constructor
    64      * Constructor
    61      * @throws IOException
    65      */
    62      */
    66     protected CodeStore() {
    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     }
    67     }
    78 
    68 
    79     @Override
    69     @Override
    80     public DebugLogger initLogger(final Context context) {
    70     public DebugLogger initLogger(final Context context) {
    81          return context.getLogger(getClass());
    71         log = context.getLogger(getClass());
       
    72         return log;
    82     }
    73     }
    83 
    74 
    84     @Override
    75     @Override
    85     public DebugLogger getLogger() {
    76     public DebugLogger getLogger() {
    86         return log;
    77         return log;
    87     }
    78     }
    88 
    79 
    89     private static File checkDirectory(final String path) throws IOException {
    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;
    90         try {
    89         try {
    91             return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
    90             // security check first
    92                 @Override
    91             final SecurityManager sm = System.getSecurityManager();
    93                 public File run() throws IOException {
    92             if (sm != null) {
    94                     final File dir = new File(path).getAbsoluteFile();
    93                 sm.checkPermission(new RuntimePermission(NASHORN_PROVIDE_CODE_STORE));
    95                     if (!dir.exists() && !dir.mkdirs()) {
    94             }
    96                         throw new IOException("Could not create directory: " + dir.getPath());
    95             final ServiceLoader<CodeStore> services = ServiceLoader.load(baseClass);
    97                     } else if (!dir.isDirectory()) {
    96             final Iterator<CodeStore> iterator = services.iterator();
    98                         throw new IOException("Not a directory: " + dir.getPath());
    97             if (iterator.hasNext()) {
    99                     } else if (!dir.canRead() || !dir.canWrite()) {
    98                 final CodeStore store = iterator.next();
   100                         throw new IOException("Directory not readable or writable: " + dir.getPath());
    99                 store.initLogger(context).info("using code store provider ", store.getClass().getCanonicalName());
   101                     }
   100                 return store;
   102                     return dir;
   101             }
   103                 }
   102         } catch (final AccessControlException e) {
   104             });
   103             context.getLogger(CodeStore.class).warning("failed to load code store provider ", e);
   105         } catch (final PrivilegedActionException e) {
   104         }
   106             throw (IOException) e.getException();
   105         final CodeStore store = new DirectoryCodeStore();
   107         }
   106         store.initLogger(context);
   108     }
   107         return store;
   109 
   108     }
   110     private File getCacheFile(final Source source, final String functionKey) {
   109 
   111         return new File(dir, source.getDigest() + '-' + functionKey);
   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);
   112     }
   175     }
   113 
   176 
   114     /**
   177     /**
   115      * Generate a string representing the function with {@code functionId} and {@code paramTypes}.
   178      * Generate a string representing the function with {@code functionId} and {@code paramTypes}.
   116      * @param functionId function id
   179      * @param functionId function id
   127         }
   190         }
   128         return b.toString();
   191         return b.toString();
   129     }
   192     }
   130 
   193 
   131     /**
   194     /**
   132      * Return a compiled script from the cache, or null if it isn't found.
   195      * A store using a file system directory.
   133      *
   196      */
   134      * @param source the source
   197     public static class DirectoryCodeStore extends CodeStore {
   135      * @param functionKey the function key
   198 
   136      * @return the stored script or null
   199         // Default minimum size for storing a compiled script class
   137      */
   200         private final static int DEFAULT_MIN_SIZE = 1000;
   138     public StoredScript loadScript(final Source source, final String functionKey) {
   201 
   139         if (source.getLength() < minSize) {
   202         private final File dir;
   140             return null;
   203         private final boolean readOnly;
   141         }
   204         private final int minSize;
   142 
   205 
   143         final File file = getCacheFile(source, functionKey);
   206         /**
   144 
   207          * Constructor
   145         try {
   208          *
   146             return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() {
   209          * @throws IOException
   147                 @Override
   210          */
   148                 public StoredScript run() throws IOException, ClassNotFoundException {
   211         public DirectoryCodeStore() throws IOException {
   149                     if (!file.exists()) {
   212             this(Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE);
   150                         return null;
   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;
   151                     }
   248                     }
   152                     try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
   249                 });
   153                         final StoredScript storedScript = (StoredScript) in.readObject();
   250             } catch (final PrivilegedActionException e) {
   154                         getLogger().info("loaded ", source, "-", functionKey);
   251                 throw (IOException) e.getException();
   155                         return storedScript;
   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                         }
   156                     }
   275                     }
   157                 }
   276                 });
   158             });
   277             } catch (final PrivilegedActionException e) {
   159         } catch (final PrivilegedActionException e) {
   278                 getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException());
   160             getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException());
   279                 return null;
   161             return null;
   280             }
   162         }
   281         }
   163     }
   282 
   164 
   283         @Override
   165     /**
   284         public StoredScript store(final String functionKey, final Source source, final StoredScript script) {
   166      * Store a compiled script in the cache.
   285             if (readOnly || script == null || belowThreshold(source)) {
   167      *
   286                 return null;
   168      * @param functionKey the function key
   287             }
   169      * @param source the source
   288 
   170      * @param mainClassName the main class name
   289             final File file = getCacheFile(source, functionKey);
   171      * @param classBytes a map of class bytes
   290 
   172      * @param constants the constants array
   291             try {
   173      */
   292                 return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() {
   174     public void storeScript(final String functionKey, final Source source, final String mainClassName, final Map<String, byte[]> classBytes,
   293                     @Override
   175                           final Map<Integer, FunctionInitializer> initializers, final Object[] constants, final int compilationId) {
   294                     public StoredScript run() throws IOException {
   176         if (source.getLength() < minSize) {
   295                         try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
   177             return;
   296                             out.writeObject(script);
   178         }
   297                         }
   179         for (final Object constant : constants) {
   298                         getLogger().info("stored ", source, "-", functionKey);
   180             // Make sure all constant data is serializable
   299                         return script;
   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                     }
   300                     }
   197                     getLogger().info("stored ", source, "-", functionKey);
   301                 });
   198                     return null;
   302             } catch (final PrivilegedActionException e) {
   199                 }
   303                 getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException());
   200             });
   304                 return null;
   201         } catch (final PrivilegedActionException e) {
   305             }
   202             getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException());
   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;
   203         }
   319         }
   204     }
   320     }
   205 }
   321 }
   206 
   322