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();