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