From 1962206a01ada5b0edbaa13f2a9841d83937c27e Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Tue, 4 Nov 2014 10:02:51 -0500 Subject: [PATCH] Implement DiskLruCache.evictAll and expose it in Cache. Closes https://github.com/square/okhttp/issues/257 --- .../java/com/squareup/okhttp/CacheTest.java | 161 ++++++++++++------ .../okhttp/internal/DiskLruCacheTest.java | 85 ++++++++- .../main/java/com/squareup/okhttp/Cache.java | 8 + .../okhttp/internal/DiskLruCache.java | 77 ++++++--- 4 files changed, 250 insertions(+), 81 deletions(-) diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/CacheTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/CacheTest.java index 38a53ad9d3d4..f489cc9cdd05 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/CacheTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/CacheTest.java @@ -268,7 +268,8 @@ private void testResponseCaching(TransferKind transferKind) throws IOException { } @Test public void responseCachingAndRedirects() throws Exception { - server.enqueue(new MockResponse().addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + server.enqueue(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) .addHeader("Location: /foo")); @@ -276,7 +277,8 @@ private void testResponseCaching(TransferKind transferKind) throws IOException { .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) .setBody("ABC")); - server.enqueue(new MockResponse().setBody("DEF")); + server.enqueue(new MockResponse() + .setBody("DEF")); Request request = new Request.Builder().url(server.getUrl("/")).build(); Response response1 = client.newCall(request).execute(); @@ -291,7 +293,9 @@ private void testResponseCaching(TransferKind transferKind) throws IOException { } @Test public void redirectToCachedResult() throws Exception { - server.enqueue(new MockResponse().addHeader("Cache-Control: max-age=60").setBody("ABC")); + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .setBody("ABC")); server.enqueue(new MockResponse() .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) .addHeader("Location: /foo")); @@ -332,7 +336,8 @@ private void testResponseCaching(TransferKind transferKind) throws IOException { .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) .setBody("ABC")); - server.enqueue(new MockResponse().setBody("DEF")); + server.enqueue(new MockResponse() + .setBody("DEF")); client.setSslSocketFactory(sslContext.getSocketFactory()); client.setHostnameVerifier(NULL_HOSTNAME_VERIFIER); @@ -365,7 +370,8 @@ private void testResponseCaching(TransferKind transferKind) throws IOException { .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) .setBody("ABC")); - server2.enqueue(new MockResponse().setBody("DEF")); + server2.enqueue(new MockResponse() + .setBody("DEF")); server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) @@ -518,7 +524,8 @@ private void testClientPrematureDisconnect(TransferKind transferKind) throws IOE .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS)) .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS)) .setBody("A")); - server.enqueue(new MockResponse().setBody("B")); + server.enqueue(new MockResponse() + .setBody("B")); URL url = server.getUrl("/?foo=bar"); assertEquals("A", get(url).body().string()); @@ -535,15 +542,18 @@ private void testClientPrematureDisconnect(TransferKind transferKind) throws IOE } @Test public void expirationDateInThePastWithNoLastModifiedHeader() throws Exception { - assertNotCached(new MockResponse().addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); + assertNotCached(new MockResponse() + .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); } @Test public void expirationDateInTheFuture() throws Exception { - assertFullyCached(new MockResponse().addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); + assertFullyCached(new MockResponse() + .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); } @Test public void maxAgePreferredWithMaxAgeAndExpires() throws Exception { - assertFullyCached(new MockResponse().addHeader("Date: " + formatDate(0, TimeUnit.HOURS)) + assertFullyCached(new MockResponse() + .addHeader("Date: " + formatDate(0, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Cache-Control: max-age=60")); } @@ -561,7 +571,8 @@ private void testClientPrematureDisconnect(TransferKind transferKind) throws IOE @Test public void maxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception { // Chrome interprets max-age relative to the local clock. Both our cache // and Firefox both use the earlier of the local and server's clock. - assertNotCached(new MockResponse().addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS)) + assertNotCached(new MockResponse() + .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS)) .addHeader("Cache-Control: max-age=60")); } @@ -577,26 +588,28 @@ private void testClientPrematureDisconnect(TransferKind transferKind) throws IOE } @Test public void maxAgeWithLastModifiedButNoServedDate() throws Exception { - assertFullyCached( - new MockResponse().addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) - .addHeader("Cache-Control: max-age=60")); + assertFullyCached(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) + .addHeader("Cache-Control: max-age=60")); } @Test public void maxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception { - assertFullyCached( - new MockResponse().addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) - .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS)) - .addHeader("Cache-Control: max-age=60")); + assertFullyCached(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) + .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS)) + .addHeader("Cache-Control: max-age=60")); } @Test public void maxAgePreferredOverLowerSharedMaxAge() throws Exception { - assertFullyCached(new MockResponse().addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES)) + assertFullyCached(new MockResponse() + .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES)) .addHeader("Cache-Control: s-maxage=60") .addHeader("Cache-Control: max-age=180")); } @Test public void maxAgePreferredOverHigherMaxAge() throws Exception { - assertNotCached(new MockResponse().addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES)) + assertNotCached(new MockResponse() + .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES)) .addHeader("Cache-Control: s-maxage=180") .addHeader("Cache-Control: max-age=60")); } @@ -705,8 +718,9 @@ private void testMethodInvalidates(String requestMethod) throws Exception { // 1. seed the cache // 2. invalidate it with uncacheable response // 3. expect a cache miss - server.enqueue( - new MockResponse().setBody("A").addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); + server.enqueue(new MockResponse() + .setBody("A") + .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); server.enqueue(new MockResponse() .setBody("B") .setResponseCode(500)); @@ -728,8 +742,8 @@ private void testMethodInvalidates(String requestMethod) throws Exception { } @Test public void etag() throws Exception { - RecordedRequest conditionalRequest = assertConditionallyCached( - new MockResponse().addHeader("ETag: v1")); + RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() + .addHeader("ETag: v1")); assertTrue(conditionalRequest.getHeaders().contains("If-None-Match: v1")); } @@ -745,13 +759,15 @@ private void testMethodInvalidates(String requestMethod) throws Exception { } @Test public void etagAndExpirationDateInTheFuture() throws Exception { - assertFullyCached(new MockResponse().addHeader("ETag: v1") + assertFullyCached(new MockResponse() + .addHeader("ETag: v1") .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); } @Test public void cacheControlNoCache() throws Exception { - assertNotCached(new MockResponse().addHeader("Cache-Control: no-cache")); + assertNotCached(new MockResponse() + .addHeader("Cache-Control: no-cache")); } @Test public void cacheControlNoCacheAndExpirationDateInTheFuture() throws Exception { @@ -765,7 +781,8 @@ private void testMethodInvalidates(String requestMethod) throws Exception { } @Test public void pragmaNoCache() throws Exception { - assertNotCached(new MockResponse().addHeader("Pragma: no-cache")); + assertNotCached(new MockResponse() + .addHeader("Pragma: no-cache")); } @Test public void pragmaNoCacheAndExpirationDateInTheFuture() throws Exception { @@ -779,11 +796,13 @@ private void testMethodInvalidates(String requestMethod) throws Exception { } @Test public void cacheControlNoStore() throws Exception { - assertNotCached(new MockResponse().addHeader("Cache-Control: no-store")); + assertNotCached(new MockResponse() + .addHeader("Cache-Control: no-store")); } @Test public void cacheControlNoStoreAndExpirationDateInTheFuture() throws Exception { - assertNotCached(new MockResponse().addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) + assertNotCached(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) .addHeader("Cache-Control: no-store")); } @@ -796,7 +815,8 @@ private void testMethodInvalidates(String requestMethod) throws Exception { .setResponseCode(HttpURLConnection.HTTP_PARTIAL) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) .addHeader("Content-Range: bytes 1000-1001/2000")); - server.enqueue(new MockResponse().setBody("BB")); + server.enqueue(new MockResponse() + .setBody("BB")); URL url = server.getUrl("/"); @@ -848,15 +868,15 @@ private void testMethodInvalidates(String requestMethod) throws Exception { } @Test public void nonIdentityEncodingAndConditionalCache() throws Exception { - assertNonIdentityEncodingCached( - new MockResponse().addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) - .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); + assertNonIdentityEncodingCached(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) + .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); } @Test public void nonIdentityEncodingAndFullCache() throws Exception { - assertNonIdentityEncodingCached( - new MockResponse().addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) - .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); + assertNonIdentityEncodingCached(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) + .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); } private void assertNonIdentityEncodingCached(MockResponse response) throws Exception { @@ -882,9 +902,11 @@ private void assertNonIdentityEncodingCached(MockResponse response) throws Excep .addHeader("Content-Encoding: gzip") .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); - server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED) + server.enqueue(new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED) .addHeader("Content-Encoding: gzip")); - server.enqueue(new MockResponse().setBody("DEFDEFDEF")); + server.enqueue(new MockResponse() + .setBody("DEFDEFDEF")); assertEquals("ABCABCABC", get(server.getUrl("/")).body().string()); assertEquals("ABCABCABC", get(server.getUrl("/")).body().string()); @@ -898,7 +920,8 @@ private void assertNonIdentityEncodingCached(MockResponse response) throws Excep .addHeader("Content-Encoding: gzip") .addHeader("Vary: Accept-Encoding") .addHeader("Cache-Control: max-age=60")); - server.enqueue(new MockResponse().setBody("FAIL")); + server.enqueue(new MockResponse() + .setBody("FAIL")); assertEquals("ABCABCABC", get(server.getUrl("/")).body().string()); assertEquals("ABCABCABC", get(server.getUrl("/")).body().string()); @@ -906,7 +929,8 @@ private void assertNonIdentityEncodingCached(MockResponse response) throws Excep @Test public void conditionalCacheHitIsNotDoublePooled() throws Exception { server.enqueue(new MockResponse() - .addHeader("ETag: v1").setBody("A")); + .addHeader("ETag: v1") + .setBody("A")); server.enqueue(new MockResponse() .clearHeaders() .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); @@ -921,9 +945,9 @@ private void assertNonIdentityEncodingCached(MockResponse response) throws Excep } @Test public void expiresDateBeforeModifiedDate() throws Exception { - assertConditionallyCached( - new MockResponse().addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) - .addHeader("Expires: " + formatDate(-2, TimeUnit.HOURS))); + assertConditionallyCached(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Expires: " + formatDate(-2, TimeUnit.HOURS))); } @Test public void requestMaxAge() throws IOException { @@ -932,7 +956,8 @@ private void assertNonIdentityEncodingCached(MockResponse response) throws Excep .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); - server.enqueue(new MockResponse().setBody("B")); + server.enqueue(new MockResponse() + .setBody("B")); assertEquals("A", get(server.getUrl("/")).body().string()); @@ -949,7 +974,8 @@ private void assertNonIdentityEncodingCached(MockResponse response) throws Excep .setBody("A") .addHeader("Cache-Control: max-age=60") .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES))); - server.enqueue(new MockResponse().setBody("B")); + server.enqueue(new MockResponse() + .setBody("B")); assertEquals("A", get(server.getUrl("/")).body().string()); @@ -1006,7 +1032,8 @@ private void assertNonIdentityEncodingCached(MockResponse response) throws Excep .setBody("A") .addHeader("Cache-Control: max-age=120, must-revalidate") .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES))); - server.enqueue(new MockResponse().setBody("B")); + server.enqueue(new MockResponse() + .setBody("B")); assertEquals("A", get(server.getUrl("/")).body().string()); @@ -1308,7 +1335,8 @@ private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String @Test public void statisticsFullCacheHit() throws Exception { server.enqueue(new MockResponse() - .addHeader("Cache-Control: max-age=60").setBody("A")); + .addHeader("Cache-Control: max-age=60") + .setBody("A")); assertEquals("A", get(server.getUrl("/")).body().string()); assertEquals(1, cache.getRequestCount()); @@ -1494,7 +1522,8 @@ private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String } @Test public void varyMultipleFieldValuesWithMatch() throws Exception { - server.enqueue(new MockResponse().addHeader("Cache-Control: max-age=60") + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Accept-Language") .setBody("A")); server.enqueue(new MockResponse() @@ -1519,10 +1548,12 @@ private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String } @Test public void varyMultipleFieldValuesWithNoMatch() throws Exception { - server.enqueue(new MockResponse().addHeader("Cache-Control: max-age=60") + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Accept-Language") .setBody("A")); - server.enqueue(new MockResponse().setBody("B")); + server.enqueue(new MockResponse() + .setBody("B")); URL url = server.getUrl("/"); Request request1 = new Request.Builder() @@ -1543,8 +1574,10 @@ private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String } @Test public void varyAsterisk() throws Exception { - server.enqueue( - new MockResponse().addHeader("Cache-Control: max-age=60").addHeader("Vary: *").setBody("A")); + server.enqueue( new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: *") + .setBody("A")); server.enqueue(new MockResponse() .setBody("B")); @@ -1622,7 +1655,8 @@ private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Cache-Control: max-age=0") .setBody("A")); - server.enqueue(new MockResponse().addHeader("Transfer-Encoding: none") + server.enqueue(new MockResponse() + .addHeader("Transfer-Encoding: none") .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); Response response1 = get(server.getUrl("/")); @@ -1679,7 +1713,8 @@ public void assertCookies(URL url, String... expectedCookies) throws Exception { } @Test public void cachePlusRange() throws Exception { - assertNotCached(new MockResponse().setResponseCode(HttpURLConnection.HTTP_PARTIAL) + assertNotCached(new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_PARTIAL) .addHeader("Date: " + formatDate(0, TimeUnit.HOURS)) .addHeader("Content-Range: bytes 100-100/200") .addHeader("Cache-Control: max-age=60")); @@ -1837,6 +1872,20 @@ public void assertCookies(URL url, String... expectedCookies) throws Exception { assertEquals("foo", response.header("etag")); } + @Test public void evictAll() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .setBody("A")); + server.enqueue(new MockResponse() + .setBody("B")); + + URL url = server.getUrl("/"); + assertEquals("A", get(url).body().string()); + client.getCache().evictAll(); + assertEquals(0, client.getCache().getSize()); + assertEquals("B", get(url).body().string()); + } + private Response get(URL url) throws IOException { Request request = new Request.Builder() .url(url) @@ -1883,9 +1932,11 @@ private RecordedRequest assertConditionallyCached(MockResponse response) throws .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); // scenario 2: condition fails - server.enqueue(response.setBody("B").setStatus("HTTP/1.1 200 B-OK")); + server.enqueue(response.setBody("B") + .setStatus("HTTP/1.1 200 B-OK")); server.enqueue(new MockResponse() - .setStatus("HTTP/1.1 200 C-OK").setBody("C")); + .setStatus("HTTP/1.1 200 C-OK") + .setBody("C")); URL valid = server.getUrl("/valid"); Response response1 = get(valid); diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/DiskLruCacheTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/DiskLruCacheTest.java index e0fa1b034920..935a5af13610 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/DiskLruCacheTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/DiskLruCacheTest.java @@ -271,6 +271,16 @@ public X(T t) { assertFalse(k1.exists()); } + @Test public void removePreventsActiveEditFromStoringAValue() throws Exception { + set("a", "a", "a"); + DiskLruCache.Editor a = cache.edit("a"); + a.set(0, "a1"); + assertTrue(cache.remove("a")); + a.set(1, "a2"); + a.commit(); + assertAbsent("a"); + } + /** * Each read sees a snapshot of the file at the time read was called. * This means that two reads of the same key can see different data. @@ -893,8 +903,81 @@ public X(T t) { assertNull(cache.get("a")); } + /** + * We had a long-lived bug where {@link DiskLruCache#trimToSize} could + * infinite loop if entries being edited required deletion for the operation + * to complete. + */ + @Test public void trimToSizeWithActiveEdit() throws Exception { + set("a", "a1234", "a1234"); + DiskLruCache.Editor a = cache.edit("a"); + a.set(0, "a123"); + + cache.setMaxSize(8); // Smaller than the sum of active edits! + cache.flush(); // Force trimToSize(). + assertEquals(0, cache.size()); + assertNull(cache.get("a")); + + // After the edit is completed, its entry is still gone. + a.set(1, "a1"); + a.commit(); + assertAbsent("a"); + assertEquals(0, cache.size()); + } + + @Test public void evictAll() throws Exception { + set("a", "a", "a"); + set("b", "b", "b"); + cache.evictAll(); + assertEquals(0, cache.size()); + assertAbsent("a"); + assertAbsent("b"); + } + + @Test public void evictAllWithPartialCreate() throws Exception { + DiskLruCache.Editor a = cache.edit("a"); + a.set(0, "a1"); + a.set(1, "a2"); + cache.evictAll(); + assertEquals(0, cache.size()); + a.commit(); + assertAbsent("a"); + } + + @Test public void evictAllWithPartialEditDoesNotStoreAValue() throws Exception { + set("a", "a", "a"); + DiskLruCache.Editor a = cache.edit("a"); + a.set(0, "a1"); + a.set(1, "a2"); + cache.evictAll(); + assertEquals(0, cache.size()); + a.commit(); + assertAbsent("a"); + } + + @Test public void evictAllDoesntInterruptPartialRead() throws Exception { + set("a", "a", "a"); + DiskLruCache.Snapshot a = cache.get("a"); + assertEquals("a", a.getString(0)); + cache.evictAll(); + assertEquals(0, cache.size()); + assertAbsent("a"); + assertEquals("a", a.getString(1)); + a.close(); + } + + @Test public void editSnapshotAfterEvictAllReturnsNullDueToStaleValue() throws Exception { + set("a", "a", "a"); + DiskLruCache.Snapshot a = cache.get("a"); + cache.evictAll(); + assertEquals(0, cache.size()); + assertAbsent("a"); + assertNull(a.edit()); + a.close(); + } + private void assertJournalEquals(String... expectedBodyLines) throws Exception { - List expectedLines = new ArrayList(); + List expectedLines = new ArrayList<>(); expectedLines.add(MAGIC); expectedLines.add(VERSION_1); expectedLines.add("100"); diff --git a/okhttp/src/main/java/com/squareup/okhttp/Cache.java b/okhttp/src/main/java/com/squareup/okhttp/Cache.java index 373feb031312..c80e866cf6de 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Cache.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Cache.java @@ -251,6 +251,14 @@ public void delete() throws IOException { cache.delete(); } + /** + * Deletes all values stored in the cache. In-flight writes to the cache will + * complete normally, but the corresponding responses will not be stored. + */ + public void evictAll() throws IOException { + cache.evictAll(); + } + public synchronized int getWriteAbortCount() { return writeAbortCount; } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java b/okhttp/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java index 77ec7959e8fa..a5e5e4fbd6bd 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java @@ -24,7 +24,6 @@ import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -577,8 +576,9 @@ private boolean journalRebuildRequired() { } /** - * Drops the entry for {@code key} if it exists and can be removed. Entries - * actively being edited cannot be removed. + * Drops the entry for {@code key} if it exists and can be removed. If the + * entry for {@code key} is currently being edited, that edit will complete + * normally but its value will not be stored. * * @return true if an entry was removed. */ @@ -586,8 +586,13 @@ public synchronized boolean remove(String key) throws IOException { checkNotClosed(); validateKey(key); Entry entry = lruEntries.get(key); - if (entry == null || entry.currentEditor != null) { - return false; + if (entry == null) return false; + return removeEntry(entry); + } + + private boolean removeEntry(Entry entry) throws IOException { + if (entry.currentEditor != null) { + entry.currentEditor.hasErrors = true; // Prevent the edit from completing normally. } for (int i = 0; i < valueCount; i++) { @@ -598,8 +603,8 @@ public synchronized boolean remove(String key) throws IOException { } redundantOpCount++; - journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(key).writeByte('\n'); - lruEntries.remove(key); + journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(entry.key).writeByte('\n'); + lruEntries.remove(entry.key); if (journalRebuildRequired()) { executorService.execute(cleanupRunnable); @@ -632,8 +637,7 @@ public synchronized void close() throws IOException { return; // Already closed. } // Copying for safe iteration. - for (Object next : lruEntries.values().toArray()) { - Entry entry = (Entry) next; + for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) { if (entry.currentEditor != null) { entry.currentEditor.abort(); } @@ -645,8 +649,8 @@ public synchronized void close() throws IOException { private void trimToSize() throws IOException { while (size > maxSize) { - Map.Entry toEvict = lruEntries.entrySet().iterator().next(); - remove(toEvict.getKey()); + Entry toEvict = lruEntries.values().iterator().next(); + removeEntry(toEvict); } } @@ -660,6 +664,17 @@ public void delete() throws IOException { Util.deleteContents(directory); } + /** + * Deletes all stored values from the cache. In-flight edits will complete + * normally but their values will not be stored. + */ + public synchronized void evictAll() throws IOException { + // Copying for safe iteration. + for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) { + removeEntry(entry); + } + } + private void validateKey(String key) { Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); if (!matcher.matches()) { @@ -823,13 +838,15 @@ public void set(int index, String value) throws IOException { * edit lock so another edit may be started on the same key. */ public void commit() throws IOException { - if (hasErrors) { - completeEdit(this, false); - remove(entry.key); // The previous entry is stale. - } else { - completeEdit(this, true); + synchronized (DiskLruCache.this) { + if (hasErrors) { + completeEdit(this, false); + removeEntry(entry); // The previous entry is stale. + } else { + completeEdit(this, true); + } + committed = true; } - committed = true; } /** @@ -837,14 +854,18 @@ public void commit() throws IOException { * started on the same key. */ public void abort() throws IOException { - completeEdit(this, false); + synchronized (DiskLruCache.this) { + completeEdit(this, false); + } } public void abortUnlessCommitted() { - if (!committed) { - try { - abort(); - } catch (IOException ignored) { + synchronized (DiskLruCache.this) { + if (!committed) { + try { + completeEdit(this, false); + } catch (IOException ignored) { + } } } } @@ -858,7 +879,9 @@ public FaultHidingSink(Sink delegate) { try { super.write(source, byteCount); } catch (IOException e) { - hasErrors = true; + synchronized (DiskLruCache.this) { + hasErrors = true; + } } } @@ -866,7 +889,9 @@ public FaultHidingSink(Sink delegate) { try { super.flush(); } catch (IOException e) { - hasErrors = true; + synchronized (DiskLruCache.this) { + hasErrors = true; + } } } @@ -874,7 +899,9 @@ public FaultHidingSink(Sink delegate) { try { super.close(); } catch (IOException e) { - hasErrors = true; + synchronized (DiskLruCache.this) { + hasErrors = true; + } } } }