Addresses ClassNotFoundException problems when the data structures serialized in
the unit cache log files no longer matches due to changes in GWT.
Review at http://gwt-code-reviews.appspot.com/1412801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9977 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 dcff5e2..554e186 100644
--- a/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java
+++ b/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java
@@ -266,7 +266,7 @@
*/
static final String UNIT_CACHE_PREFIX = "gwt-unitCache";
- static final String CACHE_PREFIX = UNIT_CACHE_PREFIX + "-";
+ static final String CACHE_FILE_PREFIX = UNIT_CACHE_PREFIX + "-";
/**
* If there are more than this many files in the cache, clean up the old
@@ -310,7 +310,7 @@
long timestamp = System.currentTimeMillis();
do {
currentCacheFile =
- new File(cacheDirectory, CACHE_PREFIX + String.format("%016X", timestamp++));
+ new File(cacheDirectory, CACHE_FILE_PREFIX + String.format("%016X", timestamp++));
} while (currentCacheFile.exists());
// this isn't 100% reliable if multiple processes are in contention
@@ -424,7 +424,7 @@
File[] files = cacheDirectory.listFiles();
List<File> cacheFiles = new ArrayList<File>();
for (File file : files) {
- if (file.getName().startsWith(CACHE_PREFIX)) {
+ if (file.getName().startsWith(CACHE_FILE_PREFIX)) {
cacheFiles.add(file);
}
}
@@ -460,6 +460,7 @@
if (cacheFile.equals(currentCacheFile)) {
continue;
}
+ boolean deleteCacheFile = false;
try {
fis = new FileInputStream(cacheFile);
bis = new BufferedInputStream(fis);
@@ -487,15 +488,21 @@
} catch (EOFException ex) {
// Go on to the next file.
} catch (IOException ex) {
- logger.log(TreeLogger.WARN, "Error reading cache file: " + cacheFile.getAbsolutePath(),
- ex);
+ deleteCacheFile = true;
+ logger.log(TreeLogger.TRACE, "Ignoring and deleting cache log "
+ + cacheFile.getAbsolutePath() + " due to read error.", ex);
} catch (ClassNotFoundException ex) {
- logger.log(TreeLogger.ERROR, "Error deserializing CompilationUnit in "
- + cacheFile.getAbsolutePath(), ex);
+ deleteCacheFile = true;
+ logger.log(TreeLogger.TRACE, "Ignoring and deleting cache log "
+ + cacheFile.getAbsolutePath() + " due to deserialization error.", ex);
} finally {
Utility.close(inputStream);
Utility.close(bis);
Utility.close(fis);
+ }
+ if (deleteCacheFile) {
+ cacheFile.delete();
+ } else {
logger.log(TreeLogger.TRACE, cacheFile.getName() + ": Load complete");
}
}
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 ba4fefe..27908ed 100644
--- a/dev/core/test/com/google/gwt/dev/javac/PersistentUnitCacheTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/PersistentUnitCacheTest.java
@@ -22,13 +22,32 @@
import junit.framework.TestCase;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
/**
* Unit test for {@link PersistentUnitCache}.
*/
public class PersistentUnitCacheTest extends TestCase {
+ private static class ThrowsClassNotFoundException implements Serializable {
+ @SuppressWarnings("unused")
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ throw new ClassNotFoundException();
+ }
+ }
+
+ private static class ThrowsIOException implements Serializable {
+ @SuppressWarnings("unused")
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ throw new IOException();
+ }
+ }
+
File lastCacheDir = null;
public void tearDown() {
@@ -39,6 +58,41 @@
}
/**
+ * When a cache file encounters a serialization error, the logic should assume
+ * the cache log is stale and remove it.
+ */
+ public void testClassNotFoundException() throws IOException, UnableToCompleteException,
+ InterruptedException {
+ checkInvalidObjectInCache(new ThrowsClassNotFoundException());
+ }
+
+ /**
+ * Test if a file already exists with the name we want to put the cache dir
+ * in.
+ */
+ public void testFileInTheWay() throws IOException {
+ TreeLogger logger = TreeLogger.NULL;
+ File fileInTheWay = File.createTempFile("PersistentUnitTest-inTheWay", "");
+ assertNotNull(fileInTheWay);
+ assertTrue(fileInTheWay.exists());
+ fileInTheWay.deleteOnExit();
+ try {
+ new PersistentUnitCache(logger, fileInTheWay);
+ fail("Expected an exception to be thrown");
+ } catch (UnableToCompleteException expected) {
+ }
+ }
+
+ /**
+ * If a cache file has some kind of IO exception, (this can happen with a
+ * stale cache file), then the exception should be ignored and the cache file
+ * removed.
+ */
+ public void testIOException() throws IOException, UnableToCompleteException, InterruptedException {
+ checkInvalidObjectInCache(new ThrowsIOException());
+ }
+
+ /**
* The cache should recursively create the directories it needs.
*/
public void testNewDir() throws IOException, UnableToCompleteException {
@@ -52,37 +106,13 @@
assertTrue(newDir.isDirectory());
}
- /**
- * Test if a file already exists with the name we want to put the
- * cache dir in.
- */
- public void testFileInTheWay() throws IOException {
- TreeLogger logger = TreeLogger.NULL;
- File fileInTheWay = File.createTempFile("PersistentUnitTest-inTheWay", "");
- assertNotNull(fileInTheWay);
- assertTrue(fileInTheWay.exists());
- fileInTheWay.deleteOnExit();
- try {
- new PersistentUnitCache(logger, fileInTheWay);
- fail("Expected an exception to be thrown");
- } catch (UnableToCompleteException expected) {
- }
- }
-
public void testPersistentCache() throws IOException, InterruptedException,
UnableToCompleteException {
TreeLogger logger = TreeLogger.NULL;
- File cacheDir = null;
- lastCacheDir = cacheDir = File.createTempFile("persistentCacheTest", "");
- assertNotNull(cacheDir);
- // Wait, this needs to be a directory, not a file.
- assertTrue(cacheDir.delete());
- // directory will get cleaned up in tearDown()
- assertTrue(cacheDir.mkdir());
+ File cacheDir = lastCacheDir = File.createTempFile("persistentCacheTest", "");
+ File unitCacheDir = mkCacheDir(cacheDir);
- File unitCacheDir = new File(cacheDir, PersistentUnitCache.UNIT_CACHE_PREFIX);
- assertNull(unitCacheDir.list());
PersistentUnitCache cache = new PersistentUnitCache(logger, cacheDir);
MockCompilationUnit foo1 = new MockCompilationUnit("com.example.Foo", "Foo: source1");
@@ -204,4 +234,38 @@
private void assertNumCacheFiles(File unitCacheDir, int expected) {
assertEquals(expected, unitCacheDir.list().length);
}
+
+ private void checkInvalidObjectInCache(Object toSerialize) throws IOException,
+ FileNotFoundException, UnableToCompleteException, InterruptedException {
+ TreeLogger logger = TreeLogger.NULL;
+ File cacheDir = lastCacheDir = File.createTempFile("PersistentUnitTest-CNF", "");
+ File unitCacheDir = mkCacheDir(cacheDir);
+
+ /*
+ * 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");
+ ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(errorFile));
+ os.writeObject(toSerialize);
+ os.close();
+
+ assertNumCacheFiles(unitCacheDir, 1);
+
+ PersistentUnitCache cache = new PersistentUnitCache(logger, cacheDir);
+ cache.cleanup(logger);
+ cache.shutdown();
+
+ // The bogus file should have been removed.
+ assertNumCacheFiles(unitCacheDir, 0);
+ }
+
+ private File mkCacheDir(File cacheDir) {
+ assertNotNull(cacheDir);
+ assertTrue(cacheDir.exists());
+ cacheDir.delete();
+ File unitCacheDir = new File(cacheDir, PersistentUnitCache.UNIT_CACHE_PREFIX);
+ unitCacheDir.mkdirs();
+ return unitCacheDir;
+ }
}