Make persitent unit cache files be different for each compiler version. Persistent unit cache filenames now include a hash of the compiler proper to avoid deserializing state that was serialized by a different version of the compiler. Fix for issue 7794. Change-Id: I6b6d0d2cafd367806ff0ea2c86ae2895aeaa1e50
diff --git a/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java b/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java index 3c54f49..24703c6 100644 --- a/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java +++ b/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java
@@ -18,11 +18,14 @@ import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.dev.jjs.InternalCompilerException; +import com.google.gwt.dev.jjs.ast.JNode; import com.google.gwt.dev.jjs.impl.GwtAstBuilder; import com.google.gwt.dev.util.log.speedtracer.DevModeEventType; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; import com.google.gwt.util.tools.Utility; +import com.google.gwt.thirdparty.guava.common.hash.Hashing; +import com.google.gwt.thirdparty.guava.common.io.Files; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -33,6 +36,8 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.net.JarURLConnection; +import java.net.URLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -64,34 +69,34 @@ * number of files in the directory is reached * {@link PersistentUnitCache#CACHE_FILE_THRESHOLD} , the cache files are * consolidated back into a single file. - * + * * <p> * System Properties (see {@link UnitCacheFactory}). - * + * * <ul> * <li>gwt.persistentunitcache : enables the persistent cache (eventually will * be default)</li> * <li>gwt.persistentunitcachedir=<dir>: sets or overrides the cache directory</li> * </ul> - * + * * <p> * Known Issues: - * + * * <ul> * <li>This design uses an eager cache to load every unit in the cache on the * first reference to find() or add(). When the cache is large (10000 units), it * uses lots of heap and takes 5-10 seconds. Once the PersistentUnitCache is * created, it starts eagerly loading the cache in a background thread).</li> - * + * * <li>Although units logged to disk with the same resource path are eventually * cleaned up, the most recently compiled unit stays in the cache forever. This * means that stale units that are no longer referenced will never be purged, * unless by some external action (e.g. ant clean).</li> - * + * * <li>Unless ant builds are made aware of the cache directory, the cache will * persist if a user does an ant clean.</li> * </ul> - * + * */ class PersistentUnitCache extends MemoryUnitCache { @@ -100,12 +105,13 @@ * files. */ static final int CACHE_FILE_THRESHOLD = 40; - + /** * Common prefix for creating directories and cache files. */ static final String UNIT_CACHE_PREFIX = "gwt-unitCache"; static final String CACHE_FILE_PREFIX = UNIT_CACHE_PREFIX + "-"; + static final String CURRENT_VERSION_CACHE_FILE_PREFIX = computePersistentCacheFilenamePrefix(); /** * Creates a new file with a name based on the current system time. @@ -116,7 +122,8 @@ long timestamp = System.currentTimeMillis(); try { do { - newFile = new File(cacheDirectory, CACHE_FILE_PREFIX + String.format("%016X", timestamp++)); + newFile = new File(cacheDirectory, CURRENT_VERSION_CACHE_FILE_PREFIX + + String.format("%016X", timestamp++)); } while (!newFile.createNewFile()); } catch (IOException ex) { logger.log(TreeLogger.WARN, "Unable to create new cache log file " @@ -135,16 +142,17 @@ /** * Finds all files matching a pattern in the cache directory. - * + * * @return an array of sorted filenames. The file name pattern is such that * sorting them alphabetically also sorts the files by age. */ - private static File[] getCacheFiles(File cacheDirectory) { + private static File[] getCacheFiles(File cacheDirectory, boolean currentVersion) { if (cacheDirectory.isDirectory()) { File[] files = cacheDirectory.listFiles(); List<File> cacheFiles = new ArrayList<File>(); for (File file : files) { - if (file.getName().startsWith(CACHE_FILE_PREFIX)) { + if (file.getName().startsWith(currentVersion ? CURRENT_VERSION_CACHE_FILE_PREFIX : + CACHE_FILE_PREFIX)) { cacheFiles.add(file); } } @@ -161,7 +169,7 @@ */ private Future<Boolean> purgeTaskStatus; private AtomicBoolean purgeInProgress = new AtomicBoolean(false); - + private final Runnable purgeOldCacheFilesTask = new Runnable() { @Override public void run() { @@ -169,7 +177,7 @@ // Delete all cache files in the directory except for the currently open // file. SpeedTracerLogger.Event deleteEvent = SpeedTracerLogger.start(DevModeEventType.DELETE_CACHE); - File[] filesToDelete = getCacheFiles(cacheDirectory); + File[] filesToDelete = getCacheFiles(cacheDirectory, false); logger.log(TreeLogger.TRACE, "Purging cache files from " + cacheDirectory); for (File toDelete : filesToDelete) { if (!currentCacheFile.equals(toDelete)) { @@ -179,14 +187,14 @@ } } deleteEvent.end(); - + rotateCurrentCacheFile(); } catch (UnableToCompleteException e) { backgroundService.shutdownNow(); } finally { purgeInProgress.set(false); } - } + } }; private final Runnable rotateCacheFilesTask = new Runnable() { @@ -215,7 +223,7 @@ * Saved to be able to wait for UNIT_MAP_LOAD_TASK to complete. */ private Future<Boolean> unitMapLoadStatus; - + private final Runnable unitMapLoadTask = new Runnable() { @Override public void run() { @@ -227,7 +235,7 @@ * Used to execute the above Runnables in a background thread. */ private final ExecutorService backgroundService; - + private int unitsWritten = 0; private int addedSinceLastCleanup = 0; @@ -302,7 +310,7 @@ } } }); - + /** * Load up cached units from the persistent store in the background. The * {@link #add(CompilationUnit)} and {@link #find(String)} methods block if @@ -340,7 +348,7 @@ /** * Cleans up old cache files in the directory, migrating everything previously * loaded in them to the current cache file. - * + * * Normally, only newly compiled units are written to the current log, but * when it is time to cleanup, valid units from older log files need to be * re-written. @@ -353,10 +361,11 @@ return; } boolean shouldRotate = addedSinceLastCleanup > 0; - logger.log(TreeLogger.TRACE, "Added " + addedSinceLastCleanup + " units to cache since last cleanup."); + logger.log(TreeLogger.TRACE, "Added " + addedSinceLastCleanup + + " units to cache since last cleanup."); addedSinceLastCleanup = 0; try { - File[] cacheFiles = getCacheFiles(cacheDirectory); + File[] cacheFiles = getCacheFiles(cacheDirectory, true); if (cacheFiles.length < CACHE_FILE_THRESHOLD) { if (shouldRotate) { backgroundService.execute(rotateCacheFilesTask); @@ -376,7 +385,7 @@ return; } } - + /* * Resend all units read in from the in-memory cache to the background * thread. They will be re-written out and the old cache files removed. @@ -438,7 +447,7 @@ /** * For Unit testing - shutdown the persistent cache. - * + * * @throws ExecutionException * @throws InterruptedException */ @@ -452,6 +461,25 @@ } } + private static String computePersistentCacheFilenamePrefix() { + try { + URLConnection urlConnection = + JNode.class.getResource("JNode.class").openConnection(); + if (urlConnection instanceof JarURLConnection) { + String gwtdevJar = ((JarURLConnection) urlConnection).getJarFile().getName(); + return CACHE_FILE_PREFIX + + Files.hash(new File(gwtdevJar), Hashing.sha1()).toString() + "-"; + } + System.err.println("Could not find the GWT compiler jarfile. " + + "Serialization errors might occur when accessing the persistent unit cache."); + } catch (IOException e) { + System.err.println("Could not compute the hash for the GWT compiler jarfile." + + "Serialization errors might occur when accessing the persistent unit cache."); + e.printStackTrace(); + } + return CACHE_FILE_PREFIX; + } + private void addImpl(final UnitCacheEntry entry) { try { backgroundService.execute(new Runnable() { @@ -514,7 +542,7 @@ } try { if (cacheDirectory.isDirectory() && cacheDirectory.canRead()) { - File[] files = getCacheFiles(cacheDirectory); + File[] files = getCacheFiles(cacheDirectory, true); for (File cacheFile : files) { FileInputStream fis = null; BufferedInputStream bis = null; @@ -589,7 +617,8 @@ } else { logger .log(TreeLogger.TRACE, - "Starting with empty Cache: CompilationUnit cache directory does not exist or is not readable."); + "Starting with empty Cache: CompilationUnit cache " + + "directory does not exist or is not readable."); } } finally { loadPersistentUnitEvent.end();
diff --git a/dev/core/test/com/google/gwt/dev/javac/PersistentUnitCacheTest.java b/dev/core/test/com/google/gwt/dev/javac/PersistentUnitCacheTest.java index d8ce8ef..09ac9ae 100644 --- a/dev/core/test/com/google/gwt/dev/javac/PersistentUnitCacheTest.java +++ b/dev/core/test/com/google/gwt/dev/javac/PersistentUnitCacheTest.java
@@ -248,7 +248,8 @@ * Create a cache file that has the right filename, but the wrong kind of * object in it. */ - File errorFile = new File(unitCacheDir, PersistentUnitCache.CACHE_FILE_PREFIX + "12345"); + File errorFile = new File(unitCacheDir, + PersistentUnitCache.CURRENT_VERSION_CACHE_FILE_PREFIX + "12345"); ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(errorFile)); os.writeObject(toSerialize); os.close();